diff --git a/multiapps-controller-client/pom.xml b/multiapps-controller-client/pom.xml index ce3ba16217..19dfc96ea3 100644 --- a/multiapps-controller-client/pom.xml +++ b/multiapps-controller-client/pom.xml @@ -85,17 +85,19 @@ value provided - - - org.cloudfoundry - cloudfoundry-client-reactor - - + io.projectreactor.netty reactor-netty + + + + commons-logging + commons-logging + ${commons-logging.version} + @@ -103,6 +105,22 @@ micrometer-core ${micrometer.version} + + + + org.springframework + spring-test + ${spring.version} + test + + + + + org.hamcrest + hamcrest + ${hamcrest.version} + test + @@ -139,38 +157,6 @@ - - - org.cloudfoundry - cloudfoundry-client-reactor - ${cloudfoundry-client.version} - - - - org.slf4j - jcl-over-slf4j - - - - com.fasterxml.jackson.core - jackson-databind - - - - - - commons-logging - commons-logging - ${commons-logging.version} - - \ No newline at end of file diff --git a/multiapps-controller-client/src/main/java/module-info.java b/multiapps-controller-client/src/main/java/module-info.java index 4201bc0a0b..1600dc2c0b 100644 --- a/multiapps-controller-client/src/main/java/module-info.java +++ b/multiapps-controller-client/src/main/java/module-info.java @@ -12,34 +12,33 @@ exports org.cloudfoundry.multiapps.controller.client.facade.util; exports org.cloudfoundry.multiapps.controller.client.facade.dto; - requires transitive org.cloudfoundry.client; requires spring.security.oauth2.core; requires transitive spring.web; requires com.fasterxml.jackson.databind; + requires java.net.http; + requires io.netty.handler; + requires io.netty.transport; requires org.apache.commons.collections4; requires org.apache.commons.io; requires org.apache.commons.logging; - requires org.cloudfoundry.client.reactor; requires org.cloudfoundry.multiapps.common; - requires org.cloudfoundry.util; + requires org.reactivestreams; requires org.slf4j; - requires java.net.http; - requires spring.core; - requires spring.webflux; requires reactor.core; + requires reactor.netty; requires reactor.netty.core; requires reactor.netty.http; - requires org.reactivestreams; - requires io.netty.handler; - requires io.netty.transport; - requires reactor.netty; + requires spring.core; requires spring.security.core; requires spring.security.oauth2.client; + requires spring.webflux; requires static com.fasterxml.jackson.annotation; requires static java.compiler; requires static jakarta.inject; requires static org.immutables.value; + requires io.netty.codec; + requires org.apache.logging.log4j; } \ No newline at end of file diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java new file mode 100644 index 0000000000..7d06f44b7e --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java @@ -0,0 +1,65 @@ +package org.cloudfoundry.multiapps.controller; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +public class Constants { + + public static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofMinutes(2); + public static final Duration JOB_POLL_MIN_INTERVAL = Duration.ofSeconds(1); + public static final Duration JOB_POLL_MAX_INTERVAL = Duration.ofSeconds(15); + public static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); + public static final Duration BINDING_OPERATIONS_TIMEOUT = Duration.ofMinutes(10); + public static final long PACKAGE_UPLOAD_JOB_POLLING_PERIOD = TimeUnit.SECONDS.toMillis(5); + + public static final int DEFAULT_CONNECTION_POOL_SIZE = 192; + public static final int UNDEFINED_PORT = -1; + public static final int MAX_CONCURRENT_PAGES = 4; //leaving it like that for testing purposes + public static final int SECOND_PAGE = 2; + public static final int DEFAULT_CONCURRENT_TASKS = 8; //this one can be a bit more, but first tests will be like that + public static final int MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST = 4000; + public static final int JOB_POLL_INTERVAL_MULTIPLIER = 2; + + public static final String CONNECTION_POOL_NAME = "cf-controller-client"; + public static final String LOOP_RESOURCES_SUFFIX = "-loop-resources"; + public static final String UPLOAD_MONITOR_THREAD_NAME_PREFIX = "app-upload-monitor-"; + public static final String CF_API_V3 = "/v3"; + public static final String HREF = "href"; + public static final String CLOUD_CONTROLLER_CF_ROOT_DOCUMENT_NAME = "cloud_controller_v3"; + public static final String LOG_CACHE_CF_ROOT_DOCUMENT_NAME = "log_cache"; + public static final String API_HOST_PREFIX = "api."; + public static final String LOG_CACHE_PREFIX = "log-cache."; + public static final String ROOT_DOCUMENT_LINKS_LIST = "links"; + public static final String TYPE = "type"; + public static final String DATA = "data"; + public static final String CLOUD_NATIVE_BUILDPACK = "cnb"; + + public static final String EMPTY_STRING = ""; + public static final String COLON = ":"; + public static final String COMMA = ","; + public static final String SPACE_SYMBOL = " "; + public static final String EQUALS_SYMBOL = "="; + public static final String PROTOCOL_SEPARATOR = "://"; + public static final String QUESTION_MARK = "?"; + public static final String AMPERSAND = "&"; + public static final String NEW_LINE = "\n"; + public static final String SLASH = "/"; + + public static final String PACKAGE_LINK = "package"; + public static final String WEB_PROCESS_TYPE = "web"; + + public static final String USER_PROVIDED = "user-provided"; + + public static final String JOB_STATE_COMPLETE = "COMPLETE"; + public static final String JOB_STATE_FAILED = "FAILED"; + + public static final String PER_PAGE = "per_page="; + + public static final char OPEN_BRACE_CHAR = '{'; + public static final char CLOSE_BRACE_CHAR = '}'; + + private Constants() { + + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java index af0d03d5f0..4c081e732f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java @@ -3,9 +3,66 @@ public class Messages { // INFO messages - public static final String WAITING_MS_BEFORE_RETRYING_WITH_TIMEOUT_OF_MS = "Waiting: {} ms before retrying with timeout of: {} ms"; + public static final String WAITING_MS_BEFORE_RETRYING_WITH_TIMEOUT_OF_MS = "Waiting: {} ms before retrying with timeout of: {} ms."; public static final String RATE_LIMITED_BY_CC_WAITING_S = "CC returned 429 with Retry-After: {} s. Waiting {} s (capped) before retrying."; public static final String RATE_LIMITED_BY_CC_NO_HEADER_WAITING_MS = "CC returned 429 without Retry-After header. Waiting {} ms before retrying."; public static final String RANDOM_WAIT_BEFORE_RETRY_MS = "Waiting {} ms (randomized) before retrying failed CC operation."; + // WARN messages + public static final String COULD_NOT_PARSE_GUID_STRING = "Could not parse GUID string: \"{0}\""; + public static final String COULD_NOT_PARSE_DATE_STRING = "Could not parse date string: \"{0}\""; + + // Exception messages + public static final String ERROR_OCCURRED_SETTING_UP_DEFAULT_SSL_CONTEXT = "An error occurred while setting up the default SSLContext."; + public static final String ERROR_OCCURRED_SETTING_UP_ALWAYS_APPROVING_SSL_CONTEXT = "An error occurred while setting up the always approving SSLContext."; + public static final String COULD_NOT_DETERMINE_API_ORIGIN_FROM_0 = "Could not determine the API origin from \"{0}\"."; + public static final String COULD_NOT_RESOLVE_URL_FROM_0_1 = "Could not resolve URL from \"{0}\" \"{1}\"."; + public static final String COULD_NOT_RESOLVE_LOG_CACHE_URL_FROM_0 = "Could not derive the log-cache URL from \"{0}\"."; + public static final String CF_ROOT_DOCUMENT_REQUEST_TO_0_RETURNED_1 = "CF root request to \"{0}\" returned \"{1}\"."; + public static final String INTERRUPTED_WHILE_CALLING_THE_CF_ROOT_URL_AT_0 = "Interrupted while calling the CF root URL at \"{0}\"."; + public static final String FAILED_TO_CALL_THE_CF_ROOT_AT_0_WITH_1 = "Failed to call the CF root at \"{0}\" with: \"{1}\"."; + public static final String SERVICE_KEY_0_NOT_FOUND = "Service key \"{0}\" not found."; + public static final String SERVICE_INSTANCE_0_NOT_FOUND = "Service instance \"{0}\" not found."; + public static final String SERVICE_BROKER_0_NOT_FOUND = "Service broker \"{0}\" not found."; + public static final String APPLICATION_0_NOT_FOUND = "Application \"{0}\" not found."; + public static final String DOMAIN_0_NOT_FOUND = "Domain \"{0}\" not found."; + public static final String SERVICE_PLAN_0_NOT_FOUND = "Service plan \"{0}\" not found."; + public static final String STACK_0_NOT_FOUND = "Stack \"{0}\" not found."; + public static final String ORGANISATION_0_NOT_FOUND = "Organization \"{0}\" not found."; + public static final String SPACE_WITH_GUID_0_NOT_FOUND = "Space with GUID \"{0}\" not found."; + public static final String APPLICATION_WITH_GUID_0_DOES_NOT_HAVE_A_DROPLET = "Application with guid \"{0}\" does not have a droplet."; + public static final String BUILD_WITH_GUID_0_NOT_FOUND = "Build with guid \"{0}\" not found."; + public static final String DOMAIN_WITH_GUID_0_NOT_FOUND = "Domain with guid \"{0}\" not found."; + public static final String JOB_WITH_GUID_0_NOT_FOUND = "Job with guid \"{0}\" not found."; + public static final String STATISTICS_FOR_APPLICATION_INSTANCES_WITH_GUID_0_NOT_FOUND = "Statistics for application instance with guid \"{0}\" not found."; + public static final String APPLICATION_PROCESS_FOR_APPLICATION_WITH_GUID_0_NOT_FOUND = "Application process for application with guid \"{0}\" not found."; + public static final String PARAMETERS_OF_SERVICE_BINDING_WITH_GUID_0_NOT_FOUND = "Parameters of service binding with guid \"{0}\" not found."; + public static final String PARAMETERS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND = "Parameters of service instance with guid \"{0}\" not found."; + public static final String CREDENTIALS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND = "Credentials of service instance with guid \"{0}\" not found."; + public static final String HOST_0_NOT_FOUND_FOR_DOMAIN_1 = "Host \"{0}\" not found for domain \"{1}\"."; + public static final String UNABLE_TO_0_WITHOUT_SPECIFYING_ORGANIZATION_AND_SPACE_TO_USE = "Unable to \"{0}\" without specifying organization and space to use."; + public static final String DOMAIN_0_NOT_FOUND_FOR_URI_1 = "Domain \"{0}\" not found for URI \"{1}\""; + public static final String SERVICE_BINDING_BETWEEN_SERVICE_WITH_GUID_0_AND_APPLICATION_WITH_GUID_1_NOT_FOUND = "Service binding between service with GUID \"{0}\" and application with GUID \"{1}\" not found."; + public static final String SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1 = "Service plan with guid \"{0}\" is not available for service instance \"{1}\"."; + public static final String NO_SERVICE_PLAN_FOUND = "Service plan with guid \"{0}\" for service instance with name \"{1}\" was not found."; + public static final String SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE = "Service offering with guid \"{0}\" is not available."; + public static final String SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND = "Service offering with guid \"{0}\" not found."; + public static final String TARGET_SPACE_REQUIRED_TO_CREATE_AN_APPLICATION = "A target space is required to create an application."; + public static final String JOB_0_DID_NOT_COMPLETE_WITHIN_1 = "Job \"{0}\" did not complete within \"{1}\""; + public static final String ORGANIZATION_WITH_GUID_0_NOT_FOUND = "Organization with GUID \"{0}\" not found."; + public static final String SPACE_0_NOT_FOUND_IN_ORGANIZATION_1 = "Space \"{0}\" not found in organization \"{1}\""; + public static final String INTERRUPTED_WHILE_POLLING_ASYNC_JOB_0 = "Interrupted while polling an async job: \"{0}\""; + public static final String UNKNOWN_JOB_STATE_0 = "Unknown job state: \"{0}\""; + public static final String UNKNOWN_SERVICE_INSTANCE_TYPE_0 = "Unknown service instance type: \"{0}\""; + public static final String CANNOT_CREATE_DOMAIN_0_WITHOUT_ORGANIZATION = "Cannot create domain \"{0}\" without specifying an organization."; + public static final String PACKAGE_0_NOT_FOUND = "Package \"{0}\" not found."; + public static final String BUILDPACKS_ARE_REQUIRED_FOR_THE_BUILDPACK_LIFECYCLE_TYPE = "Buildpacks are required for the buildpack (Cloud Native Buildpack) lifecycle type."; + + //Status texts + public static final String JOB_TIMEOUT = "Job Timeout"; + public static final String JOB_FAILED = "Job Failed"; + public static final String JOB_FAILED_WITHOUT_DETAILS = "Job failed without any error details"; + public static final String JOB_0_WITH_OPERATION_1_FAILED_WITH_2 = "Job \"{0}\" (operation \"{1}\") failed with: {2}"; + public static final String JOB_0_RELATED_TO_OPERATION_1_FAILED_WITH_2 = "Job \"{0}\" related to operation \"{1}\" failed with: \"{2}\""; + } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java index 9e5a2352eb..8c648a9266 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java @@ -10,7 +10,6 @@ import java.util.function.Function; import java.util.function.Supplier; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.ApplicationServicesUpdateCallback; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClientImpl; @@ -35,6 +34,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; @@ -192,11 +192,6 @@ public List getApplicationRoutes(UUID applicationGuid) { return executeWithRetry(() -> delegate.getApplicationRoutes(applicationGuid)); } - @Override - public boolean getApplicationSshEnabled(UUID applicationGuid) { - return executeWithRetry(() -> delegate.getApplicationSshEnabled(applicationGuid)); - } - @Override public Map getApplicationFeatures(UUID applicationGuid) { return executeWithRetry(() -> delegate.getApplicationFeatures(applicationGuid)); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java new file mode 100644 index 0000000000..944e54edf6 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java @@ -0,0 +1,11 @@ +package org.cloudfoundry.multiapps.controller.client.facade; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface AllowNulls { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java index 2d8f04ccdf..ac5670a127 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java @@ -8,7 +8,6 @@ import java.util.Set; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; @@ -28,6 +27,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; @@ -280,8 +280,6 @@ Optional bindServiceInstance(String bindingName, String applicationName, List getApplicationRoutes(UUID applicationGuid); - boolean getApplicationSshEnabled(UUID applicationGuid); - Map getApplicationFeatures(UUID applicationGuid); /** diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java index ca0386040c..51c9dca804 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java @@ -11,8 +11,6 @@ import java.util.UUID; import java.util.function.Supplier; -import org.cloudfoundry.AbstractCloudFoundryException; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; @@ -32,6 +30,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; @@ -40,7 +39,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerRestClient; import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.rest.ImmutableCloudControllerRestClientFactory; -import org.springframework.http.HttpStatus; import org.springframework.util.Assert; /** @@ -259,11 +257,6 @@ public List getApplicationRoutes(UUID applicationGuid) { return handleExceptions(() -> delegate.getApplicationRoutes(applicationGuid)); } - @Override - public boolean getApplicationSshEnabled(UUID applicationGuid) { - return handleExceptions(() -> delegate.getApplicationSshEnabled(applicationGuid)); - } - @Override public Map getApplicationFeatures(UUID applicationGuid) { return handleExceptions(() -> delegate.getApplicationFeatures(applicationGuid)); @@ -659,16 +652,9 @@ private void handleExceptions(Runnable runnable) { } private T handleExceptions(Supplier runnable) { - try { - return runnable.get(); - } catch (AbstractCloudFoundryException e) { - throw convertV3ClientException(e); - } - } - - private CloudOperationException convertV3ClientException(AbstractCloudFoundryException e) { - HttpStatus httpStatus = HttpStatus.valueOf(e.getStatusCode()); - return new CloudOperationException(httpStatus, httpStatus.getReasonPhrase(), e.getMessage(), e); + // The underlying CloudControllerRestClient already surfaces failures as CloudOperationException + // (via CloudControllerResponseErrorHandler), so no exception translation is needed here. + return runnable.get(); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/Messages.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/Messages.java index e4cda12b8a..af19d79b3d 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/Messages.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/Messages.java @@ -26,5 +26,6 @@ private Messages() { public static final String FAILED_TO_FETCH_APP_LOGS_FOR_APP = "Failed to fetch app logs for app: {0}"; public static final String BUILDPACKS_ARE_REQUIRED_FOR_CNB_LIFECYCLE_TYPE = "Buildpacks are required for CNB lifecycle type."; + public static final String UNKNOWN_USER_ROLE_0 = "Unknown user role: \"{0}\""; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/CloudFoundryClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/CloudFoundryClientFactory.java deleted file mode 100644 index 5461f765a8..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/CloudFoundryClientFactory.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.text.MessageFormat; -import java.time.Duration; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; - -import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v3.organizations.OrganizationsV3; -import org.cloudfoundry.client.v3.spaces.SpacesV3; -import org.cloudfoundry.reactor.ConnectionContext; -import org.cloudfoundry.reactor.DefaultConnectionContext; -import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient; -import org.cloudfoundry.reactor.client.v3.organizations.ReactorOrganizationsV3; -import org.cloudfoundry.reactor.client.v3.spaces.ReactorSpacesV3; -import org.immutables.value.Value; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; - -import org.cloudfoundry.multiapps.controller.client.facade.CloudException; -import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.Messages; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; -import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudSpaceClient; -import org.cloudfoundry.multiapps.controller.client.facade.util.CloudUtil; -import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; - -import reactor.core.publisher.Mono; - -@Value.Immutable -public abstract class CloudFoundryClientFactory { - - private static final Logger LOGGER = LoggerFactory.getLogger(CloudFoundryClientFactory.class); - - static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() - .executor(Executors.newSingleThreadExecutor()) - .followRedirects(HttpClient.Redirect.NORMAL) - .connectTimeout(Duration.ofMinutes(10)) - .build(); - - private final Map connectionContextCache = new ConcurrentHashMap<>(); - - public abstract Optional getSslHandshakeTimeout(); - - public abstract Optional getConnectTimeout(); - - public abstract Optional getConnectionPoolSize(); - - public abstract Optional getThreadPoolSize(); - - public abstract Optional getResponseTimeout(); - - public CloudFoundryClient createClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - return ReactorCloudFoundryClient.builder() - .connectionContext(getOrCreateConnectionContext(controllerUrl.getHost())) - .tokenProvider(oAuthClient.getTokenProvider()) - .requestTags(requestTags) - .build(); - } - - public LogCacheClient createLogCacheClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - return new LogCacheClient(oAuthClient, requestTags, getOrCreateConnectionContext(controllerUrl.getHost())); - } - - @SuppressWarnings("unchecked") - private Map callCfRoot(URL controllerUrl, Map requestTags) { - HttpResponse response; - try { - HttpRequest request = buildCfRootRequest(controllerUrl, requestTags); - LOGGER.info(MessageFormat.format(Messages.CALLING_CF_ROOT_0_TO_ACCESS_LOG_CACHE_URL, controllerUrl)); - response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() / 100 != 2) { - var status = HttpStatus.valueOf(response.statusCode()); - throw new CloudOperationException(status, status.getReasonPhrase(), response.body()); - } - LOGGER.info(Messages.CF_ROOT_REQUEST_FINISHED); - } catch (InterruptedException | URISyntaxException | IOException e) { - throw new CloudException(e.getMessage(), e); - } - var map = JsonUtil.convertJsonToMap(response.body()); - return (Map) map.get("links"); - } - - private HttpRequest buildCfRootRequest(URL controllerUrl, Map requestTags) throws URISyntaxException { - var requestBuilder = HttpRequest.newBuilder() - .GET() - .uri(controllerUrl.toURI()) - .timeout(Duration.ofMinutes(5)); - requestTags.forEach(requestBuilder::header); - return requestBuilder.build(); - } - - public CloudSpaceClient createSpaceClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - String v3Api; - try { - var links = CloudUtil.executeWithRetry(() -> callCfRoot(controllerUrl, requestTags)); - @SuppressWarnings("unchecked") - var ccv3 = (Map) links.get("cloud_controller_v3"); - v3Api = (String) ccv3.get("href"); - } catch (CloudException e) { - LOGGER.warn(MessageFormat.format(Messages.CALL_TO_0_FAILED_WITH_1, controllerUrl.toString(), e.getMessage()), e); - v3Api = controllerUrl + "/v3"; - } - var spacesV3 = createV3SpacesClient(controllerUrl, v3Api, oAuthClient, requestTags); - var orgsV3 = createV3OrgsClient(controllerUrl, v3Api, oAuthClient, requestTags); - return new CloudSpaceClient(spacesV3, orgsV3); - } - - private SpacesV3 createV3SpacesClient(URL controllerUrl, String v3Api, OAuthClient oAuthClient, Map requestTags) { - return new ReactorSpacesV3(getOrCreateConnectionContext(controllerUrl.getHost()), - Mono.just(v3Api), - oAuthClient.getTokenProvider(), - requestTags); - } - - private OrganizationsV3 createV3OrgsClient(URL controllerUrl, String v3Api, OAuthClient oAuthClient, Map requestTags) { - return new ReactorOrganizationsV3(getOrCreateConnectionContext(controllerUrl.getHost()), - Mono.just(v3Api), - oAuthClient.getTokenProvider(), - requestTags); - } - - public ConnectionContext getOrCreateConnectionContext(String controllerApiHost) { - return connectionContextCache.computeIfAbsent(controllerApiHost, this::createConnectionContext); - } - - private ConnectionContext createConnectionContext(String controllerApiHost) { - DefaultConnectionContext.Builder builder = DefaultConnectionContext.builder() - .apiHost(controllerApiHost); - getSslHandshakeTimeout().ifPresent(builder::sslHandshakeTimeout); - getConnectTimeout().ifPresent(builder::connectTimeout); - getConnectionPoolSize().ifPresent(builder::connectionPoolSize); - getThreadPoolSize().ifPresent(builder::threadPoolSize); - builder.additionalHttpClientConfiguration(this::getAdditionalHttpClientConfiguration); - return builder.build(); - } - - private reactor.netty.http.client.HttpClient getAdditionalHttpClientConfiguration(reactor.netty.http.client.HttpClient client) { - var clientWithOptions = client; - if (getResponseTimeout().isPresent()) { - clientWithOptions = clientWithOptions.responseTimeout(getResponseTimeout().get()); - } - - return clientWithOptions; - } -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java index ea8d519b3f..bba82b70ea 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java @@ -1,19 +1,5 @@ package org.cloudfoundry.multiapps.controller.client.facade.adapters; -import org.cloudfoundry.multiapps.controller.client.facade.CloudException; -import org.cloudfoundry.multiapps.controller.client.facade.Messages; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ApplicationLog; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableApplicationLog; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; -import org.cloudfoundry.logcache.v1.Envelope; -import org.cloudfoundry.logcache.v1.EnvelopeType; -import org.cloudfoundry.logcache.v1.ReadRequest; -import org.cloudfoundry.logcache.v1.ReadResponse; -import org.cloudfoundry.reactor.ConnectionContext; -import org.cloudfoundry.reactor.logcache.v1.ReactorLogCacheClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.time.Duration; @@ -24,77 +10,79 @@ import java.util.Base64; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.UUID; +import org.cloudfoundry.multiapps.controller.client.facade.CloudException; +import org.cloudfoundry.multiapps.controller.client.facade.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ApplicationLog; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableApplicationLog; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.LogCacheReadResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.client.RestClient; + public class LogCacheClient { private static final Logger LOGGER = LoggerFactory.getLogger(LogCacheClient.class); private static final String SOURCE_TYPE_KEY_NAME = "source_type"; private static final int MAX_LOG_COUNT = 1000; - private final OAuthClient oAuthClient; - private final Map requestTags; - private final ConnectionContext connectionContext; - - public LogCacheClient(OAuthClient oAuthClient, Map requestTags, ConnectionContext connectionContext) { - this.oAuthClient = oAuthClient; - this.requestTags = requestTags; - this.connectionContext = connectionContext; + + private static final String READ_LOGS_PATH = "/api/v1/read/{sourceId}"; + private static final String ENVELOPE_TYPES_QUERY_PARAM = "envelope_types"; + private static final String LOG_ENVELOPE_TYPE = "LOG"; + private static final String DESCENDING_QUERY_PARAM = "descending"; + private static final String LIMIT_QUERY_PARAM = "limit"; + private static final String START_TIME_QUERY_PARAM = "start_time"; + + private final RestClient restClient; + + public LogCacheClient(RestClient restClient) { + this.restClient = restClient; } public List getRecentLogs(UUID applicationGuid, LocalDateTime offset) { LOGGER.info(Messages.TRYING_TO_GET_APP_LOGS); - org.cloudfoundry.logcache.v1.LogCacheClient logCacheClient = createReactorLogCacheClient(); - ReadResponse applicationLogsResponse = readApplicationLogs(logCacheClient, applicationGuid, offset); - - if (applicationLogsResponse != null) { - LOGGER.info(Messages.APP_LOGS_WERE_FETCHED_SUCCESSFULLY); - return applicationLogsResponse.getEnvelopes() - .getBatch() - .stream() - .map(this::mapToAppLog) - // we use a linked list so that the log messages can be a LIFO sequence - // that way, we avoid unnecessary sorting and copying to and from another collection/array - .collect(LinkedList::new, LinkedList::addFirst, LinkedList::addAll); - } else { + LogCacheReadResponse response = readApplicationLogs(applicationGuid, offset); + + if (response == null) { throw new CloudException(MessageFormat.format(Messages.FAILED_TO_FETCH_APP_LOGS_FOR_APP, applicationGuid)); } + LOGGER.info(Messages.APP_LOGS_WERE_FETCHED_SUCCESSFULLY); + return response.batch() + .stream() + .map(this::mapToAppLog) + // we use a linked list so that the log messages can be a LIFO sequence + // that way, we avoid unnecessary sorting and copying to and from another collection/array + .collect(LinkedList::new, LinkedList::addFirst, LinkedList::addAll); } - private ReactorLogCacheClient createReactorLogCacheClient() { - return ReactorLogCacheClient.builder() - .requestTags(requestTags) - .connectionContext(connectionContext) - .tokenProvider(oAuthClient.getTokenProvider()) - .build(); - } - - private ReadResponse readApplicationLogs(org.cloudfoundry.logcache.v1.LogCacheClient logCacheClient, UUID applicationGuid, - LocalDateTime offset) { + private LogCacheReadResponse readApplicationLogs(UUID applicationGuid, LocalDateTime offset) { var instant = offset.toInstant(ZoneOffset.UTC); var secondsInNanos = Duration.ofSeconds(instant.getEpochSecond()) .toNanos(); - return logCacheClient.read(ReadRequest.builder() - .envelopeType(EnvelopeType.LOG) - .sourceId(applicationGuid.toString()) - .descending(Boolean.TRUE) - .limit(MAX_LOG_COUNT) - .startTime(secondsInNanos + instant.getNano() + 1) - .build()) - .block(); + long startTime = secondsInNanos + instant.getNano() + 1; + + return restClient.get() + .uri(uriBuilder -> uriBuilder.path(READ_LOGS_PATH) + .queryParam(ENVELOPE_TYPES_QUERY_PARAM, LOG_ENVELOPE_TYPE) + .queryParam(DESCENDING_QUERY_PARAM, Boolean.TRUE) + .queryParam(LIMIT_QUERY_PARAM, MAX_LOG_COUNT) + .queryParam(START_TIME_QUERY_PARAM, startTime) + .build(applicationGuid.toString())) + .retrieve() + .body(LogCacheReadResponse.class); } - private ApplicationLog mapToAppLog(Envelope envelope) { + private ApplicationLog mapToAppLog(LogCacheReadResponse.Envelope envelope) { return ImmutableApplicationLog.builder() - .applicationGuid(envelope.getSourceId()) - .message(decodeLogPayload(envelope.getLog() - .getPayload())) - .timestamp(fromLogTimestamp(envelope.getTimestamp())) - .messageType(fromLogMessageType(envelope.getLog() - .getType() - .getValue())) - .sourceName(envelope.getTags() + .applicationGuid(envelope.sourceId()) + .message(decodeLogPayload(envelope.log() + .payload())) + .timestamp(fromLogTimestamp(envelope.timestamp())) + .messageType(fromLogMessageType(envelope.log() + .type())) + .sourceName(envelope.tags() .get(SOURCE_TYPE_KEY_NAME)) .build(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProvider.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProvider.java deleted file mode 100644 index ff5def4c97..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.reactor.ConnectionContext; -import org.cloudfoundry.reactor.TokenProvider; - -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuth2AccessTokenWithAdditionalInfo; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; - -import reactor.core.publisher.Mono; - -public class OAuthTokenProvider implements TokenProvider { - - private final OAuthClient oAuthClient; - - public OAuthTokenProvider(OAuthClient oAuthClient) { - this.oAuthClient = oAuthClient; - } - - @Override - public Mono getToken(ConnectionContext connectionContext) { - return Mono.fromSupplier(() -> { - OAuth2AccessTokenWithAdditionalInfo token = oAuthClient.getToken(); - return token.getAuthorizationHeaderValue(); - }); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java deleted file mode 100644 index 898cea1a8a..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Collections; -import java.util.Map; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableLifecycle; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle; -import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.CnbData; -import org.cloudfoundry.client.v3.LifecycleData; -import org.cloudfoundry.client.v3.applications.Application; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RawCloudApplication extends RawCloudEntity { - - public static final String BUILDPACKS = "buildpacks"; - public static final String STACK = "stack"; - - public abstract Application getApplication(); - - public abstract Derivable getSpace(); - - @Override - public CloudApplication derive() { - Application app = getApplication(); - return ImmutableCloudApplication.builder() - .metadata(parseResourceMetadata(app)) - .v3Metadata(app.getMetadata()) - .name(app.getName()) - .state(parseState(app.getState())) - .lifecycle(parseLifecycle(app.getLifecycle())) - .space(getSpace().derive()) - .build(); - } - - private static CloudApplication.State parseState(ApplicationState state) { - return CloudApplication.State.valueOf(state.getValue()); - } - - private static Lifecycle parseLifecycle(org.cloudfoundry.client.v3.Lifecycle lifecycle) { - Map data = extractLifecycleData(lifecycle.getData()); - - return ImmutableLifecycle.builder() - .type(LifecycleType.valueOf(lifecycle.getType() - .toString() - .toUpperCase())) - .data(data) - .build(); - } - - private static Map extractLifecycleData(LifecycleData lifecycleData) { - if (lifecycleData instanceof BuildpackData buildpackData) { - return Map.of( - BUILDPACKS, buildpackData.getBuildpacks(), - STACK, buildpackData.getStack()); - } else if (lifecycleData instanceof CnbData cnbData) { - return Map.of( - BUILDPACKS, cnbData.getBuildpacks(), - STACK, cnbData.getStack()); - } else { - return Collections.emptyMap(); - } - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java deleted file mode 100644 index 1e8bad359c..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.stream.Collectors; - -import org.cloudfoundry.client.v3.jobs.Job; -import org.cloudfoundry.client.v3.jobs.Warning; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; - -@Value.Immutable -public abstract class RawCloudAsyncJob extends RawCloudEntity { - - @Value.Parameter - public abstract Job getJob(); - - @Override - public CloudAsyncJob derive() { - Job job = getJob(); - return ImmutableCloudAsyncJob.builder() - .metadata(parseResourceMetadata(job)) - .state(job.getState()) - .operation(job.getOperation()) - .warnings(getWarnings(job)) - .errors(getErrors(job)) - .build(); - } - - private String getWarnings(Job job) { - return job.getWarnings() - .stream() - .map(Warning::getDetail) - .collect(Collectors.joining(",")); - } - - private String getErrors(Job job) { - return job.getErrors() - .stream() - .map(this::joinErrorDetails) - .collect(Collectors.joining(",")); - } - - private String joinErrorDetails(org.cloudfoundry.client.v3.Error error) { - return String.join(" ", error.getCode() - .toString(), - error.getTitle(), error.getDetail()); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuild.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuild.java deleted file mode 100644 index 2cdbc39eaa..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuild.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Optional; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.builds.Build; -import org.cloudfoundry.client.v3.builds.BuildState; -import org.cloudfoundry.client.v3.builds.CreatedBy; -import org.cloudfoundry.client.v3.builds.Droplet; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild.ImmutableCreatedBy; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild.ImmutablePackageInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; - -@Value.Immutable -public abstract class RawCloudBuild extends RawCloudEntity { - - private static CloudBuild.CreatedBy parseCreatedBy(Build buildResource) { - CreatedBy createdBy = buildResource.getCreatedBy(); - return ImmutableCreatedBy.builder() - .guid(parseNullableGuid(createdBy.getId())) - .name(createdBy.getName()) - .build(); - } - - private static CloudBuild.PackageInfo parsePackageInfo(Build buildResource) { - Relationship packageRelationship = buildResource.getInputPackage(); - String packageId = packageRelationship.getId(); - return ImmutablePackageInfo.of(parseNullableGuid(packageId)); - } - - private static DropletInfo parseDropletInfo(Build buildResource) { - Droplet droplet = buildResource.getDroplet(); - return Optional.ofNullable(droplet) - .map(Droplet::getId) - .map(RawCloudEntity::parseNullableGuid) - .map(dropletGuid -> ImmutableDropletInfo.builder() - .guid(dropletGuid) - .build()) - .orElse(null); - } - - private static CloudBuild.State parseState(BuildState state) { - return parseEnum(state, CloudBuild.State.class); - } - - @Value.Parameter - public abstract Build getResource(); - - @Override - public CloudBuild derive() { - Build resource = getResource(); - return ImmutableCloudBuild.builder() - .metadata(parseResourceMetadata(resource)) - .createdBy(parseCreatedBy(resource)) - .packageInfo(parsePackageInfo(resource)) - .dropletInfo(parseDropletInfo(resource)) - .state(parseState(resource.getState())) - .error(resource.getError()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomain.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomain.java deleted file mode 100644 index f03981dbaa..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomain.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.domains.Domain; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; - -@Value.Immutable -public abstract class RawCloudDomain extends RawCloudEntity { - - @Value.Parameter - public abstract Domain getResource(); - - @Override - public CloudDomain derive() { - Domain resource = getResource(); - return ImmutableCloudDomain.builder() - .metadata(parseResourceMetadata(resource)) - .name(resource.getName()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java deleted file mode 100644 index 537b1260e2..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.text.MessageFormat; -import java.time.LocalDateTime; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.Collection; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.cloudfoundry.client.v3.Resource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; - -public abstract class RawCloudEntity implements Derivable { - - private static final Logger LOGGER = LoggerFactory.getLogger(RawCloudEntity.class); - - protected RawCloudEntity() { - // Recommended by Sonar. - } - - public static CloudMetadata parseResourceMetadata(Resource resource) { - return ImmutableCloudMetadata.builder() - .guid(parseNullableGuid(resource.getId())) - .createdAt(parseNullableDate(resource.getCreatedAt())) - .updatedAt(parseNullableDate(resource.getUpdatedAt())) - .build(); - } - - protected static UUID parseNullableGuid(String guid) { - return guid == null ? null : parseGuid(guid); - } - - protected static UUID parseGuid(String guid) { - try { - return UUID.fromString(guid); - } catch (IllegalArgumentException e) { - LOGGER.warn(MessageFormat.format("Could not parse GUID string: \"{0}\"", guid), e); - return null; - } - } - - protected static LocalDateTime parseNullableDate(String date) { - return date == null ? null : parseDate(date); - } - - protected static LocalDateTime parseDate(String dateString) { - try { - return ZonedDateTime.parse(dateString, DateTimeFormatter.ISO_DATE_TIME) - .toLocalDateTime(); - } catch (DateTimeParseException e) { - LOGGER.warn(MessageFormat.format("Could not parse date string: \"{0}\"", dateString), e); - return null; - } - } - - protected static > E parseEnum(Enum value, Class targetEnum) { - String name = value.name() - .toUpperCase(); - return Enum.valueOf(targetEnum, name); - } - - protected static D deriveFromNullable(Derivable derivable) { - return derivable == null ? null : derivable.derive(); - } - - protected static Collection derive(Collection> derivables) { - return derivables.stream() - .map(Derivable::derive) - .collect(Collectors.toList()); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEvent.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEvent.java deleted file mode 100644 index 7a0937f970..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEvent.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.auditevents.AuditEventActor; -import org.cloudfoundry.client.v3.auditevents.AuditEventResource; -import org.cloudfoundry.client.v3.auditevents.AuditEventTarget; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent.Participant; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent.ImmutableParticipant; - -@Value.Immutable -public abstract class RawCloudEvent extends RawCloudEntity { - - @Value.Parameter - public abstract AuditEventResource getResource(); - - @Override - public CloudEvent derive() { - AuditEventResource resource = getResource(); - return ImmutableCloudEvent.builder() - .metadata(parseResourceMetadata(resource)) - .target(parseTarget(resource)) - .actor(parseActor(resource)) - .type(resource.getType()) - .build(); - } - - private static Participant parseTarget(AuditEventResource resource) { - AuditEventTarget target = resource.getAuditEventTarget(); - if (target == null) { - return ImmutableParticipant.builder() - .build(); - } - return ImmutableParticipant.builder() - .guid(parseNullableGuid(target.getId())) - .name(target.getName()) - .type(target.getType()) - .build(); - } - - private static Participant parseActor(AuditEventResource resource) { - AuditEventActor actor = resource.getAuditEventActor(); - if (actor == null) { - return ImmutableParticipant.builder() - .build(); - } - return ImmutableParticipant.builder() - .guid(parseNullableGuid(actor.getId())) - .name(actor.getName()) - .type(actor.getType()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackage.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackage.java deleted file mode 100644 index 73933bf781..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackage.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Checksum; -import org.cloudfoundry.client.v3.packages.BitsData; -import org.cloudfoundry.client.v3.packages.DockerData; -import org.cloudfoundry.client.v3.packages.Package; -import org.cloudfoundry.client.v3.packages.PackageType; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableBitsData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; - -@Value.Immutable -public abstract class RawCloudPackage extends RawCloudEntity { - - @Value.Parameter - public abstract Package getResource(); - - @Override - public CloudPackage derive() { - Package resource = getResource(); - return ImmutableCloudPackage.builder() - .metadata(parseResourceMetadata(resource)) - .status(parseStatus(resource)) - .data(parseData(resource)) - .type(parseType(resource)) - .build(); - } - - private static Status parseStatus(Package resource) { - return parseEnum(resource.getState(), Status.class); - } - - private static CloudPackage.PackageData parseData(Package resource) { - if (resource.getType() == PackageType.BITS) { - return parseBitsData((BitsData) resource.getData()); - } - return parseDockerData((DockerData) resource.getData()); - } - - private static CloudPackage.PackageData parseBitsData(BitsData data) { - return ImmutableBitsData.builder() - .checksum(parseBitsChecksum(data.getChecksum())) - .error(data.getError()) - .build(); - } - - private static org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData.Checksum parseBitsChecksum(Checksum checksum) { - if (checksum == null) { - return null; - } - return ImmutableBitsData.ImmutableChecksum.builder() - .algorithm(checksum.getType() - .toString()) - .value(checksum.getValue()) - .build(); - } - - private static CloudPackage.PackageData parseDockerData(DockerData data) { - return ImmutableDockerData.builder() - .image(data.getImage()) - .username(data.getUsername()) - .password(data.getPassword()) - .build(); - } - - private static CloudPackage.Type parseType(Package resource) { - return parseEnum(resource.getType(), CloudPackage.Type.class); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java deleted file mode 100644 index 931f528b75..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.processes.Data; -import org.cloudfoundry.client.v3.processes.HealthCheck; -import org.cloudfoundry.client.v3.processes.Process; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheck; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; -import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudProcess; -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RawCloudProcess extends RawCloudEntity { - - @Value.Parameter - public abstract Process getProcess(); - - @Override - public CloudProcess derive() { - Process process = getProcess(); - HealthCheck healthCheck = process.getHealthCheck(); - ReadinessHealthCheck readinessHealthCheckType = process.getReadinessHealthCheck(); - Integer healthCheckTimeout = null; - String healthCheckHttpEndpoint = null; - Integer healthCheckInvocationTimeout = null; - Integer healthCheckInterval = null; - if (healthCheck.getData() != null) { - Data healthCheckData = healthCheck.getData(); - healthCheckTimeout = healthCheckData.getTimeout(); - healthCheckInvocationTimeout = healthCheckData.getInvocationTimeout(); - healthCheckHttpEndpoint = healthCheckData.getEndpoint(); - healthCheckInterval = healthCheckData.getInterval(); - } - Integer readinessHealthCheckInvocationTimeout = null; - String readinessHealthCheckHttpEndpoint = null; - Integer readinessHealthCheckInterval = null; - if (readinessHealthCheckType.getData() != null) { - Data readinessHealthCheckData = readinessHealthCheckType.getData(); - readinessHealthCheckInvocationTimeout = readinessHealthCheckData.getInvocationTimeout(); - readinessHealthCheckHttpEndpoint = readinessHealthCheckData.getEndpoint(); - readinessHealthCheckInterval = readinessHealthCheckData.getInterval(); - } - return ImmutableCloudProcess.builder() - .command(process.getCommand()) - .instances(process.getInstances()) - .memoryInMb(process.getMemoryInMb()) - .diskInMb(process.getDiskInMb()) - .healthCheckType(HealthCheckType.valueOf(healthCheck.getType() - .getValue() - .toUpperCase())) - .healthCheckHttpEndpoint(healthCheckHttpEndpoint) - .healthCheckTimeout(healthCheckTimeout) - .healthCheckInvocationTimeout(healthCheckInvocationTimeout) - .healthCheckInterval(healthCheckInterval) - .readinessHealthCheckType(readinessHealthCheckType.getType() - .getValue()) - .readinessHealthCheckHttpEndpoint(readinessHealthCheckHttpEndpoint) - .readinessHealthCheckInvocationTimeout(readinessHealthCheckInvocationTimeout) - .readinessHealthCheckInterval(readinessHealthCheckInterval) - .build(); - } -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRoute.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRoute.java deleted file mode 100644 index e2ae758814..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRoute.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.cloudfoundry.client.v3.routes.Route; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.Nullable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableRouteDestination; -import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; - -@Value.Immutable -public abstract class RawCloudRoute extends RawCloudEntity { - - @Value.Parameter - public abstract Route getRoute(); - - @Nullable - public abstract UUID getApplicationGuid(); - - @Override - public CloudRoute derive() { - Route route = getRoute(); - List destinations = mapDestinations(); - String domainGuid = route.getRelationships() - .getDomain() - .getData() - .getId(); - return ImmutableCloudRoute.builder() - .metadata(parseResourceMetadata(route)) - .appsUsingRoute(route.getDestinations() - .size()) - .host(route.getHost()) - .port(route.getPort()) - .domain(ImmutableCloudDomain.builder() - .name(computeDomain(route)) - .metadata(ImmutableCloudMetadata.of(UUID.fromString(domainGuid))) - .build()) - .path(route.getPath()) - .url(route.getUrl()) - .destinations(destinations) - .requestedProtocol(computeRequestedProtocol(destinations)) - .build(); - } - - private static String computeDomain(Route route) { - String domain = route.getUrl(); - if (!route.getHost() - .isEmpty()) { - domain = domain.substring(route.getHost() - .length() - + 1); - } - if (!route.getPath() - .isEmpty()) { - domain = domain.substring(0, domain.indexOf('/')); - } - if (route.getPort() != null) { - domain = domain.substring(0, domain.indexOf(':')); - } - return domain; - } - - private List mapDestinations() { - return getRoute().getDestinations() - .stream() - .map(destination -> ImmutableRouteDestination.builder() - .metadata(ImmutableCloudMetadata.builder() - .guid(UUID.fromString(destination.getDestinationId())) - .build()) - .applicationGuid(UUID.fromString(destination.getApplication() - .getApplicationId())) - .port(destination.getPort()) - .weight(destination.getWeight()) - .protocol(destination.getProtocol()) - .build()) - .collect(Collectors.toList()); - } - - private String computeRequestedProtocol(List destinations) { - UUID applicationGuid = getApplicationGuid(); - if (applicationGuid == null) { - return null; - } - return destinations.stream() - .filter(destination -> Objects.equals(destination.getApplicationGuid(), applicationGuid)) - .findFirst() - .map(RouteDestination::getProtocol) - .orElse(null); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java deleted file mode 100644 index ab9ba4cad8..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.UUID; - -import org.cloudfoundry.client.v3.servicebindings.ServiceBinding; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; - -@Value.Immutable -public abstract class RawCloudServiceBinding extends RawCloudEntity { - - @Value.Parameter - public abstract ServiceBindingResource getServiceBinding(); - - @Override - public CloudServiceBinding derive() { - ServiceBinding serviceBinding = getServiceBinding(); - var appRelationship = serviceBinding.getRelationships() - .getApplication(); - return ImmutableCloudServiceBinding.builder() - .metadata(parseResourceMetadata(serviceBinding)) - .applicationGuid(parseNullableGuid(appRelationship == null ? null - : appRelationship.getData() - .getId())) - .serviceInstanceGuid(UUID.fromString(serviceBinding.getRelationships() - .getServiceInstance() - .getData() - .getId())) - .serviceBindingOperation(ServiceCredentialBindingOperation.from(getServiceBinding().getLastOperation())) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBroker.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBroker.java deleted file mode 100644 index d76fcc5cdd..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBroker.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Optional; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBroker; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerRelationships; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; - -@Value.Immutable -public abstract class RawCloudServiceBroker extends RawCloudEntity { - - @Value.Parameter - public abstract ServiceBroker getServiceBroker(); - - @Override - public CloudServiceBroker derive() { - ServiceBroker serviceBroker = getServiceBroker(); - String spaceGuid = getSpaceGuid(serviceBroker); - return ImmutableCloudServiceBroker.builder() - .metadata(parseResourceMetadata(serviceBroker)) - .name(serviceBroker.getName()) - .url(serviceBroker.getUrl()) - .spaceGuid(spaceGuid) - .build(); - } - - private String getSpaceGuid(ServiceBroker serviceBroker) { - return Optional.ofNullable(serviceBroker.getRelationships()) - .map(ServiceBrokerRelationships::getSpace) - .map(ToOneRelationship::getData) - .map(Relationship::getId) - .orElse(null); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java deleted file mode 100644 index af4d58b936..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Optional; - -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceResource; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOffering; -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.Nullable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; - -@Value.Immutable -public abstract class RawCloudServiceInstance extends RawCloudEntity { - - @Value.Parameter - public abstract ServiceInstanceResource getResource(); - - @Nullable - public abstract ServicePlan getServicePlan(); - - @Nullable - public abstract ServiceOffering getServiceOffering(); - - @Override - public CloudServiceInstance derive() { - ServiceInstanceResource resource = getResource(); - return ImmutableCloudServiceInstance.builder() - .metadata(parseResourceMetadata(resource)) - .v3Metadata(resource.getMetadata()) - .name(resource.getName()) - .plan(getServicePlanName()) - .label(getLabelName()) - .type(resource.getType()) - .tags(resource.getTags()) - .lastOperation(ServiceOperation.fromLastOperation(resource.getLastOperation())) - .syslogDrainUrl(resource.getSyslogDrainUrl()) - .build(); - } - - private String getServicePlanName() { - return Optional.ofNullable(getServicePlan()) - .map(ServicePlan::getName) - .orElse(null); - } - - private String getLabelName() { - return Optional.ofNullable(getServiceOffering()) - .map(ServiceOffering::getName) - .orElse(null); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java deleted file mode 100644 index 97f6e8eb31..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Map; - -import org.cloudfoundry.AllowNulls; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.Nullable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; - -@Value.Immutable -public abstract class RawCloudServiceKey extends RawCloudEntity { - - public abstract ServiceBindingResource getServiceBindingResource(); - - @Nullable - @AllowNulls - public abstract Map getCredentials(); - - public abstract Derivable getServiceInstance(); - - @Override - public CloudServiceKey derive() { - ServiceBindingResource serviceBindingResource = getServiceBindingResource(); - Derivable serviceInstance = getServiceInstance(); - return ImmutableCloudServiceKey.builder() - .metadata(parseResourceMetadata(serviceBindingResource)) - .v3Metadata(serviceBindingResource.getMetadata()) - .name(serviceBindingResource.getName()) - .credentials(getCredentials()) - .serviceInstance(serviceInstance.derive()) - .serviceKeyOperation(ServiceCredentialBindingOperation.from(serviceBindingResource.getLastOperation())) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOffering.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOffering.java deleted file mode 100644 index c252aa50bd..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOffering.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; - -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingResource; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceOffering; - -@Value.Immutable -public abstract class RawCloudServiceOffering extends RawCloudEntity { - - public abstract ServiceOfferingResource getServiceOffering(); - - public abstract List> getServicePlans(); - - @Override - public CloudServiceOffering derive() { - ServiceOfferingResource serviceOffering = getServiceOffering(); - return ImmutableCloudServiceOffering.builder() - .metadata(parseResourceMetadata(serviceOffering)) - .name(serviceOffering.getName()) - .isAvailable(serviceOffering.getAvailable()) - .isBindable(serviceOffering.getBrokerCatalog() - .getFeatures() - .getBindable()) - .description(serviceOffering.getDescription()) - .isShareable(serviceOffering.getShareable()) - .extra(serviceOffering.getBrokerCatalog() - .getMetadata()) - .docUrl(serviceOffering.getDocumentationUrl()) - .brokerId(serviceOffering.getRelationships() - .getServiceBroker() - .getData() - .getId()) - .uniqueId(serviceOffering.getBrokerCatalog() - .getBrokerCatalogId()) - .servicePlans(derive(getServicePlans())) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlan.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlan.java deleted file mode 100644 index a79527037e..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlan.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.cloudfoundry.client.v3.serviceplans.Visibility; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; - -@Value.Immutable -public abstract class RawCloudServicePlan extends RawCloudEntity { - - @Value.Parameter - public abstract ServicePlan getResource(); - - @Override - public CloudServicePlan derive() { - ServicePlan resource = getResource(); - return ImmutableCloudServicePlan.builder() - .metadata(parseResourceMetadata(resource)) - .name(resource.getName()) - .description(resource.getDescription()) - .extra(resource.getBrokerCatalog() - .getMetadata()) - .uniqueId(resource.getBrokerCatalog() - .getBrokerCatalogId()) - .serviceOfferingId(resource.getRelationships() - .getServiceOffering() - .getData() - .getId()) - .isPublic(resource.getVisibilityType() - .equals(Visibility.PUBLIC)) - .isFree(resource.getFree()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStack.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStack.java deleted file mode 100644 index 6c2a8ce152..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStack.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.stacks.Stack; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudStack; - -@Value.Immutable -public abstract class RawCloudStack extends RawCloudEntity { - - @Value.Parameter - public abstract Stack getStack(); - - @Override - public CloudStack derive() { - Stack stack = getStack(); - return ImmutableCloudStack.builder() - .metadata(parseResourceMetadata(stack)) - .name(stack.getName()) - .description(stack.getDescription()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTask.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTask.java deleted file mode 100644 index b963d4457a..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTask.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Optional; - -import org.cloudfoundry.client.v3.tasks.Result; -import org.cloudfoundry.client.v3.tasks.Task; -import org.cloudfoundry.client.v3.tasks.TaskState; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; - -@Value.Immutable -public abstract class RawCloudTask extends RawCloudEntity { - - @Value.Parameter - public abstract Task getResource(); - - @Override - public CloudTask derive() { - Task resource = getResource(); - return ImmutableCloudTask.builder() - .metadata(parseResourceMetadata(resource)) - .name(resource.getName()) - .command(resource.getCommand()) - .limits(parseLimits(resource)) - .result(parseResult(resource)) - .state(parseState(resource.getState())) - .build(); - } - - private static CloudTask.Result parseResult(Task resource) { - return Optional.ofNullable(resource.getResult()) - .map(Result::getFailureReason) - .map(ImmutableCloudTask.ImmutableResult::of) - .orElse(null); - } - - private static CloudTask.Limits parseLimits(Task resource) { - return ImmutableCloudTask.ImmutableLimits.builder() - .disk(resource.getDiskInMb()) - .memory(resource.getMemoryInMb()) - .build(); - } - - private static CloudTask.State parseState(TaskState state) { - return parseEnum(state, CloudTask.State.class); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java deleted file mode 100644 index 2c3a83c130..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import org.cloudfoundry.client.v3.applications.GetApplicationProcessStatisticsResponse; -import org.cloudfoundry.client.v3.processes.ProcessStatisticsResource; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstanceInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RawInstancesInfo extends RawCloudEntity { - - @Value.Parameter - public abstract GetApplicationProcessStatisticsResponse getProcessStatisticsResponse(); - - @Override - public InstancesInfo derive() { - var processStats = getProcessStatisticsResponse(); - return ImmutableInstancesInfo.builder() - .instances(parseProcessStatistics(processStats.getResources())) - .build(); - } - - private static List parseProcessStatistics(List stats) { - if (stats == null) { - return Collections.emptyList(); - } - return stats.stream() - .map(RawInstancesInfo::parseProcessStatistic) - .collect(Collectors.toList()); - } - - private static InstanceInfo parseProcessStatistic(ProcessStatisticsResource statsResource) { - return ImmutableInstanceInfo.builder() - .index(statsResource.getIndex()) - .state(InstanceState.valueOfWithDefault(statsResource.getState())) - .isRoutable(Boolean.parseBoolean(statsResource.getRoutable())) - .build(); - } -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRole.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRole.java deleted file mode 100644 index 8eacc03f55..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRole.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.client.v3.roles.RoleResource; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; - -@Value.Immutable -public abstract class RawUserRole implements Derivable { - - @Value.Parameter - public abstract RoleResource getRoleResource(); - - @Override - public UserRole derive() { - RoleResource role = getRoleResource(); - return UserRole.fromRoleType(role.getType()); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java deleted file mode 100644 index 8155a4ad41..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstance; -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RawV3CloudServiceInstance extends RawCloudEntity { - - @Value.Parameter - public abstract ServiceInstance getServiceInstance(); - - @Override - public CloudServiceInstance derive() { - ServiceInstance serviceInstance = getServiceInstance(); - return ImmutableCloudServiceInstance.builder() - .metadata(parseResourceMetadata(serviceInstance)) - .v3Metadata(serviceInstance.getMetadata()) - .name(serviceInstance.getName()) - .tags(serviceInstance.getTags()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java index 394a40c3b7..20b52febc8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java @@ -1,6 +1,5 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; -import org.cloudfoundry.client.v3.jobs.JobState; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java index 16bf0aef9b..0b12510362 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java @@ -2,14 +2,11 @@ import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; - import org.cloudfoundry.multiapps.controller.client.facade.Nullable; /** * Do not extend {@code Derivable} in this interface. It is tempting, because all of its children have the same implementation, but * implementing the {@code derive()} method here leads to this bug: https://github.com/immutables/immutables/issues/1045 - * */ public abstract class CloudEntity { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java index ef141ae3fe..ff053f4e5d 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java @@ -10,6 +10,7 @@ @JsonDeserialize(as = ImmutableCloudProcess.class) public abstract class CloudProcess extends CloudEntity implements Derivable { + @Nullable public abstract String getCommand(); public abstract Integer getDiskInMb(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceBinding.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceBinding.java index bceb94223b..e3fb8544fe 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceBinding.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceBinding.java @@ -2,7 +2,7 @@ import java.util.UUID; -import org.cloudfoundry.Nullable; +import org.cloudfoundry.multiapps.controller.client.facade.Nullable; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java index 78db202828..2cf5216efa 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java @@ -3,8 +3,7 @@ import java.util.List; import java.util.Map; -import org.cloudfoundry.AllowNulls; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.AllowNulls; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceKey.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceKey.java index a77cad1967..76c211eeda 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceKey.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceKey.java @@ -2,7 +2,7 @@ import java.util.Map; -import org.cloudfoundry.AllowNulls; +import org.cloudfoundry.multiapps.controller.client.facade.AllowNulls; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java index 6cd1a86842..d6d3bf9971 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java @@ -5,6 +5,7 @@ public enum HealthCheckType { @Override public String toString() { - return this.name().toLowerCase(); + return this.name() + .toLowerCase(); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java index 89d88d803d..a5425130ed 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java @@ -1,16 +1,14 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; -import org.cloudfoundry.client.v3.processes.ProcessState; - public enum InstanceState { CRASHED, DOWN, RUNNING, STARTING, UNKNOWN; - - public static InstanceState valueOfWithDefault(ProcessState state) { + + public static InstanceState valueOfWithDefault(String state) { if (state == null) { return UNKNOWN; } try { - return InstanceState.valueOf(state.getValue()); + return InstanceState.valueOf(state); } catch (IllegalArgumentException e) { return InstanceState.UNKNOWN; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java new file mode 100644 index 0000000000..de2a7a4de2 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java @@ -0,0 +1,7 @@ +package org.cloudfoundry.multiapps.controller.client.facade.domain; + +public enum JobState { + + PROCESSING, POLLING, COMPLETE, FAILED + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java new file mode 100644 index 0000000000..0e43b64c12 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java @@ -0,0 +1,4 @@ +package org.cloudfoundry.multiapps.controller.client.facade.domain; + +public record LastOperation(String type, String state, String description, String createdAt, String updatedAt) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Lifecycle.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Lifecycle.java index f448f2c668..d6453dd89e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Lifecycle.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Lifecycle.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.multiapps.controller.client.facade.Nullable; -import org.cloudfoundry.AllowNulls; +import org.cloudfoundry.multiapps.controller.client.facade.AllowNulls; import org.immutables.value.Value; import java.util.Map; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java new file mode 100644 index 0000000000..d2ebb31503 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java @@ -0,0 +1,25 @@ +package org.cloudfoundry.multiapps.controller.client.facade.domain; + +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.AllowNulls; +import org.immutables.value.Value; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@Value.Immutable +@JsonSerialize(as = ImmutableMetadata.class) +@JsonDeserialize(as = ImmutableMetadata.class) +public abstract class Metadata { + + @AllowNulls + @JsonProperty("labels") + public abstract Map getLabels(); + + @AllowNulls + @JsonProperty("annotations") + public abstract Map getAnnotations(); + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/RouteDestination.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/RouteDestination.java index b8b23d5093..0245919e03 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/RouteDestination.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/RouteDestination.java @@ -21,6 +21,7 @@ public abstract class RouteDestination extends CloudEntity implements Derivable< @Nullable public abstract Integer getWeight(); + @Nullable public abstract String getProtocol(); @Override diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java index 4f202834cc..73403202f1 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java @@ -5,12 +5,10 @@ import java.time.format.DateTimeFormatter; import java.util.Arrays; -import org.cloudfoundry.client.v3.LastOperation; -import org.immutables.value.Value; - import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.multiapps.controller.client.facade.Nullable; +import org.immutables.value.Value; @Value.Immutable @JsonSerialize(as = ImmutableServiceCredentialBindingOperation.class) @@ -31,11 +29,12 @@ public abstract class ServiceCredentialBindingOperation { public abstract LocalDateTime getUpdatedAt(); public static ServiceCredentialBindingOperation from(LastOperation lastOperation) { - String lastOperationType = lastOperation.getType(); - String lastOperationState = lastOperation.getState(); - String lastOperationDescription = lastOperation.getDescription(); - String lastOperationCreatedAt = lastOperation.getCreatedAt(); - String lastOperationUpdatedAt = lastOperation.getUpdatedAt(); + String lastOperationType = lastOperation.type(); + String lastOperationState = lastOperation.state(); + String lastOperationDescription = lastOperation.description(); + String lastOperationCreatedAt = lastOperation.createdAt(); + String lastOperationUpdatedAt = lastOperation.updatedAt(); + return ImmutableServiceCredentialBindingOperation.builder() .type(ServiceCredentialBindingOperation.Type.fromString(lastOperationType)) .state(ServiceCredentialBindingOperation.State.fromString(lastOperationState)) @@ -55,8 +54,9 @@ public static Type fromString(String value) { .filter(type -> type.toString() .equals(value)) .findFirst() - .orElseThrow(() -> new IllegalArgumentException(MessageFormat.format("Illegal service binding operation type: \"{0}\"", - value))); + .orElseThrow( + () -> new IllegalArgumentException(MessageFormat.format("Illegal service binding operation type: \"{0}\"", + value))); } @Override diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java new file mode 100644 index 0000000000..1e3bbefcc2 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java @@ -0,0 +1,43 @@ +package org.cloudfoundry.multiapps.controller.client.facade.domain; + +import java.text.MessageFormat; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import org.cloudfoundry.multiapps.controller.Messages; + +public enum ServiceInstanceType { + + MANAGED("managed"), USER_PROVIDED("user-provided"); + + private final String value; + + ServiceInstanceType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @JsonCreator + public static ServiceInstanceType from(String value) { + ServiceInstanceType serviceInstanceType = mapTypeToServiceInstanceType(value); + if (serviceInstanceType != null) { + return serviceInstanceType; + } + + throw new IllegalArgumentException(MessageFormat.format(Messages.UNKNOWN_SERVICE_INSTANCE_TYPE_0, value)); + } + + public static ServiceInstanceType mapTypeToServiceInstanceType(String value) { + for (ServiceInstanceType type : values()) { + if (type.value.equals(value)) { + return type; + } + } + + return null; + } +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java index 151dae6bfa..7edff81be7 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java @@ -3,8 +3,6 @@ import java.text.MessageFormat; import java.util.Objects; -import org.cloudfoundry.client.v3.LastOperation; - public class ServiceOperation { public enum Type { @@ -82,13 +80,14 @@ public State getState() { } public static ServiceOperation fromLastOperation(LastOperation lastOperation) { - if (lastOperation == null || lastOperation.getType() == null || lastOperation.getState() == null) { + if (lastOperation == null || lastOperation.type() == null || lastOperation.state() == null) { return null; } - Type type = Type.fromString(lastOperation.getType()); - State state = State.fromString(lastOperation.getState()); - String description = lastOperation.getDescription(); - return new ServiceOperation(type, description, state); + + Type operationType = Type.fromString(lastOperation.type()); + State operationState = State.fromString(lastOperation.state()); + String operationDescription = lastOperation.description(); + return new ServiceOperation(operationType, operationDescription, operationState); } @Override diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java index 3ee5e84386..225e918ed5 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java @@ -1,10 +1,11 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; +import java.text.MessageFormat; import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.roles.RoleType; +import org.cloudfoundry.multiapps.controller.client.facade.Messages; public enum UserRole { @@ -18,13 +19,15 @@ public enum UserRole { private static final Map NAMES_TO_VALUES = Arrays.stream(values()) .collect(Collectors.toMap(UserRole::getName, - roleType -> roleType)); + roleType -> roleType)); + + public static UserRole fromRoleType(String roleTypeValue) { + UserRole userRole = NAMES_TO_VALUES.get(roleTypeValue); - public static UserRole fromRoleType(RoleType roleType) { - UserRole userRole = NAMES_TO_VALUES.get(roleType.getValue()); if (userRole == null) { - throw new IllegalArgumentException("Unknown user role: " + roleType.getValue()); + throw new IllegalArgumentException(MessageFormat.format(Messages.UNKNOWN_USER_ROLE_0, roleTypeValue)); } + return userRole; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/dto/ApplicationToCreateDto.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/dto/ApplicationToCreateDto.java index e23244f2f4..c32aa17139 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/dto/ApplicationToCreateDto.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/dto/ApplicationToCreateDto.java @@ -3,7 +3,7 @@ import java.util.Map; import java.util.Set; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/oauth2/OAuthClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/oauth2/OAuthClient.java index 414fc822b8..03ae12365f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/oauth2/OAuthClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/oauth2/OAuthClient.java @@ -8,9 +8,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; import org.cloudfoundry.multiapps.controller.client.facade.Constants; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.OAuthTokenProvider; import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; -import org.cloudfoundry.reactor.TokenProvider; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; @@ -78,10 +76,6 @@ public String getAuthorizationHeaderValue() { return null; } - public TokenProvider getTokenProvider() { - return new OAuthTokenProvider(this); - } - private boolean shouldRefreshToken() { return credentials.isRefreshable() && token.getOAuth2AccessToken() .getExpiresAt() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java new file mode 100644 index 0000000000..7b3f722445 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java @@ -0,0 +1,219 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ApplicationMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class ApplicationsV3Operations { + + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public ApplicationsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public UUID createApplication(ApplicationToCreateDto dto) { + validateTargetSpace(); + + String targetSpaceGuid = targetSpace.getGuid() + .toString(); + Map body = OperationsUtil.buildApplicationBody(dto, targetSpaceGuid); + + UUID appGuid = sendCreateApplicationRequest(body); + + scaleApplicationIfNeeded(appGuid, dto); + + return appGuid; + } + + private void validateTargetSpace() { + if (targetSpace == null || targetSpace.getGuid() == null) { + throw new CloudOperationException(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.getReasonPhrase(), + Messages.TARGET_SPACE_REQUIRED_TO_CREATE_AN_APPLICATION); + } + } + + private UUID sendCreateApplicationRequest(Map body) { + V3Application created = client.postForObject(CloudControllerV3Endpoints.APPS, body, V3Application.class); + return UUID.fromString(created.guid()); + } + + private void scaleApplicationIfNeeded(UUID appGuid, ApplicationToCreateDto dto) { + Map scale = new HashMap<>(); + if (dto.getMemoryInMb() != null) { + scale.put(V3Fields.MEMORY_IN_MB, dto.getMemoryInMb()); + } + + if (dto.getDiskQuotaInMb() != null) { + scale.put(V3Fields.DISK_IN_MB, dto.getDiskQuotaInMb()); + } + + if (!scale.isEmpty()) { + scaleWebProcess(appGuid, scale); + } + } + + public void deleteApplication(String applicationName) { + UUID applicationGuid = getApplicationGuid(applicationName); + + ResponseEntity response = client.delete(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid); + + client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); + } + + public CloudApplication getApplication(String applicationName) { + return getApplication(applicationName, true); + } + + public CloudApplication getApplication(String applicationName, boolean required) { + V3Application app = findApplicationByName(applicationName); + + if (app != null) { + return V3ApplicationMapper.toCloudApplication(app, targetSpace); + } + + if (required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return null; + } + + public UUID getApplicationGuid(String applicationName) { + return getApplication(applicationName).getGuid(); + } + + public String getApplicationName(UUID applicationGuid) { + V3Application app = client.get(V3Uri.path(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) + .build(), + V3Application.class); + + return app == null ? null : app.name(); + } + + public Map getApplicationEnvironment(UUID applicationGuid) { + V3Application.V3EnvironmentVariables environmentVariablesJson = client.get(V3Uri.path(CloudControllerV3Endpoints.APP_ENV_VARS, + applicationGuid) + .build(), + V3Application.V3EnvironmentVariables.class); + + return environmentVariablesJson == null || environmentVariablesJson.environmentVariables() == null + ? Map.of() + : environmentVariablesJson.environmentVariables(); + } + + public Map getApplicationEnvironment(String applicationName) { + return getApplicationEnvironment(getApplicationGuid(applicationName)); + } + + public List getApplications() { + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(null, targetSpace); + + return listApplications(applicationsQuery).stream() + .map(app -> V3ApplicationMapper.toCloudApplication(app, targetSpace)) + .toList(); + } + + public List getApplicationsByMetadataLabelSelector(String labelSelector) { + String query = OperationsQueryUtil.buildApplicationsQuery(null, targetSpace, labelSelector); + + return listApplications(query).stream() + .map(app -> V3ApplicationMapper.toCloudApplication(app, targetSpace)) + .toList(); + } + + public void startApplication(String applicationName) { + UUID guid = getApplicationGuid(applicationName); + + client.post(CloudControllerV3Endpoints.APP_START, null, guid); + } + + public void stopApplication(String applicationName) { + UUID guid = getApplicationGuid(applicationName); + + client.post(CloudControllerV3Endpoints.APP_STOP, null, guid); + } + + public void rename(String applicationName, String newName) { + UUID guid = getApplicationGuid(applicationName); + + try { + client.patch(CloudControllerV3Endpoints.APP_BY_GUID, Map.of(V3Fields.NAME, newName), guid); + } catch (CloudOperationException e) { + //the Cloud Controller can return 503 but the rename might have happened already + if (e.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE && newName.equals(getApplicationName(guid))) { + return; + } + throw e; + } + } + + public void updateApplicationInstances(String applicationName, int instances) { + scaleWebProcess(getApplicationGuid(applicationName), Map.of(V3Fields.INSTANCES, instances)); + } + + public void updateApplicationMemory(String applicationName, int memory) { + scaleWebProcess(getApplicationGuid(applicationName), Map.of(V3Fields.MEMORY_IN_MB, memory)); + } + + public void updateApplicationDiskQuota(String applicationName, int disk) { + scaleWebProcess(getApplicationGuid(applicationName), Map.of(V3Fields.DISK_IN_MB, disk)); + } + + public void updateApplicationEnv(String applicationName, Map env) { + UUID guid = getApplicationGuid(applicationName); + + client.patch(CloudControllerV3Endpoints.APP_ENV_VARS, Map.of(V3Fields.VAR, env), guid); + } + + public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { + client.patch(CloudControllerV3Endpoints.APP_CURRENT_DROPLET, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, dropletGuid.toString())), + applicationGuid); + } + + public void updateApplicationMetadata(UUID guid, Metadata metadata) { + Map metadataMap = OperationsUtil.toMetadataMap(metadata); + client.patch(CloudControllerV3Endpoints.APP_BY_GUID, Map.of(V3Fields.METADATA, metadataMap == null ? Map.of() : metadataMap), + guid); + } + + private void scaleWebProcess(UUID applicationGuid, Map scaleBody) { + client.post(CloudControllerV3Endpoints.APP_WEB_PROCESS_SCALE, scaleBody, applicationGuid); + } + + private V3Application findApplicationByName(String applicationName) { + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = listApplications(applicationsQuery); + + return apps.isEmpty() ? null : apps.getFirst(); + } + + private List listApplications(String query) { + return client.list(query, APP_PAGE); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java new file mode 100644 index 0000000000..8fae3619db --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java @@ -0,0 +1,73 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3BuildMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +public class BuildsV3Operations { + + private static final ParameterizedTypeReference> BUILD_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + public BuildsV3Operations(CloudControllerV3Client client) { + this.client = client; + } + + public CloudBuild createBuild(UUID packageGuid) { + V3Build build = client.postForObject(CloudControllerV3Endpoints.BUILDS, + Map.of(V3Fields.PACKAGE, Map.of(V3Fields.GUID, packageGuid.toString())), V3Build.class); + + return V3BuildMapper.toCloudBuild(build); + } + + public CloudBuild getBuild(UUID buildGuid) { + V3Build build = client.get(V3Uri.path(CloudControllerV3Endpoints.BUILD_BY_GUID, buildGuid) + .build(), + V3Build.class); + + if (build == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.BUILD_WITH_GUID_0_NOT_FOUND, buildGuid)); + } + + return V3BuildMapper.toCloudBuild(build); + } + + public List getBuildsForApplication(UUID applicationGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.APP_BUILDS, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, BUILD_PAGE) + .stream() + .map(V3BuildMapper::toCloudBuild) + .toList(); + } + + public List getBuildsForPackage(UUID packageGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.BUILDS) + .query(V3QueryParameters.PACKAGE_GUIDS, packageGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, BUILD_PAGE) + .stream() + .map(V3BuildMapper::toCloudBuild) + .toList(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ClientConfigurationOptions.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ClientConfigurationOptions.java new file mode 100644 index 0000000000..5e9ef98e2d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ClientConfigurationOptions.java @@ -0,0 +1,9 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.time.Duration; +import java.util.Optional; + +public record ClientConfigurationOptions(Optional connectionTimeout, Optional responseTimeout, + Optional sslHandshakeTimeout, Optional connectionPoolSize, + Optional threadPoolSize, boolean trustSelfSignedCertificates) { +} \ No newline at end of file diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java index 8c79aec33e..5eebfa0cbc 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java @@ -8,7 +8,6 @@ import java.util.Set; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; @@ -29,6 +28,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; @@ -107,8 +107,6 @@ Optional bindServiceInstance(String bindingName, String applicationName, List getApplicationRoutes(UUID applicationGuid); - boolean getApplicationSshEnabled(UUID applicationGuid); - Map getApplicationFeatures(UUID applicationGuid); List getApplications(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java new file mode 100644 index 0000000000..ec4daf0dc3 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -0,0 +1,188 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URL; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.net.ssl.SSLException; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.channel.ChannelOption; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ReactorClientHttpRequestFactory; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; +import reactor.netty.resources.LoopResources; +import reactor.netty.tcp.SslProvider; + +public final class CloudControllerRestClientBuilder { + + private static final ObjectMapper OBJECT_MAPPER = configureObjectMapper(); + + private CloudControllerRestClientBuilder() { + } + + public static RestClient buildRestClient(HttpClient httpClient, URL baseUrl, OAuthClient oAuthClient, + Map requestTags) { + return RestClient.builder() + .baseUrl(baseUrl.toString()) + .requestFactory(new ReactorClientHttpRequestFactory(httpClient)) + .messageConverters(List.of(new ByteArrayHttpMessageConverter(), + new StringHttpMessageConverter(), + new FormHttpMessageConverter(), + new MappingJackson2HttpMessageConverter(OBJECT_MAPPER))) + .requestInterceptor(restClientAuthAndTagsInterceptor(oAuthClient, requestTags)) + .defaultStatusHandler(new CloudControllerResponseErrorHandler()) + .build(); + } + + private static ClientHttpRequestInterceptor restClientAuthAndTagsInterceptor(OAuthClient oAuthClient, Map requestTags) { + return (request, body, execution) -> { + String authorization = oAuthClient.getAuthorizationHeaderValue(); + + if (authorization != null) { + request.getHeaders() + .set(HttpHeaders.AUTHORIZATION, authorization); + } + + requestTags.forEach((String key, String value) -> request.getHeaders() + .set(key, value)); + + return execution.execute(request, body); + }; + } + + public static WebClient buildWebClient(HttpClient httpClient, URL baseUrl, OAuthClient oAuthClient, + Map requestTags) { + return WebClient.builder() + .baseUrl(baseUrl.toString()) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .filter(webClientAuthAndTagsFilter(oAuthClient, requestTags)) + .build(); + } + + private static ExchangeFilterFunction webClientAuthAndTagsFilter(OAuthClient oAuthClient, Map requestTags) { + return (request, next) -> { + ClientRequest.Builder requestBuilder = ClientRequest.from(request); + String authorizationValue = oAuthClient.getAuthorizationHeaderValue(); + + if (authorizationValue != null) { + requestBuilder.header(HttpHeaders.AUTHORIZATION, authorizationValue); + } + + requestTags.forEach(requestBuilder::header); + + return next.exchange(requestBuilder.build()); + }; + } + + public static HttpClient buildHttpClient(ClientConfigurationOptions configOptions) { + HttpClient httpClient = createBaseHttpClient(configOptions); + httpClient = configureConnectionTimeout(httpClient, configOptions); + httpClient = configureThreadPool(httpClient, configOptions); + httpClient = configureResponseTimeout(httpClient, configOptions); + httpClient = configureSsl(httpClient, configOptions); + + return httpClient; + } + + private static HttpClient createBaseHttpClient(ClientConfigurationOptions configOptions) { + int poolSize = configOptions.connectionPoolSize() + .orElse(Constants.DEFAULT_CONNECTION_POOL_SIZE); + + ConnectionProvider connectionProvider = ConnectionProvider.builder(Constants.CONNECTION_POOL_NAME) + .maxConnections(poolSize) + .build(); + + return HttpClient.create(connectionProvider) + .followRedirect(true) + .proxyWithSystemProperties(); + } + + private static HttpClient configureConnectionTimeout(HttpClient httpClient, ClientConfigurationOptions configOptions) { + int connectTimeoutMillis = (int) configOptions.connectionTimeout() + .orElse(Constants.DEFAULT_CONNECT_TIMEOUT) + .toMillis(); + + return httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis); + } + + private static HttpClient configureThreadPool(HttpClient httpClient, ClientConfigurationOptions configOptions) { + Optional threadPoolSize = configOptions.threadPoolSize(); + if (threadPoolSize.isEmpty()) { + return httpClient; + } + + LoopResources loopResources = LoopResources.create(Constants.CONNECTION_POOL_NAME + Constants.LOOP_RESOURCES_SUFFIX, + threadPoolSize.get(), true); + + return httpClient.runOn(loopResources); + } + + private static HttpClient configureResponseTimeout(HttpClient httpClient, ClientConfigurationOptions configOptions) { + Optional responseTimeout = configOptions.responseTimeout(); + if (responseTimeout.isEmpty()) { + return httpClient; + } + + return httpClient.responseTimeout(responseTimeout.get()); + } + + private static HttpClient configureSsl(HttpClient httpClient, ClientConfigurationOptions configOptions) { + Optional handshakeTimeout = configOptions.sslHandshakeTimeout(); + boolean trustSelfSignedCertificates = configOptions.trustSelfSignedCertificates(); + + if (!trustSelfSignedCertificates && handshakeTimeout.isEmpty()) { + return httpClient.secure(); + } + + SslContext sslContext = trustSelfSignedCertificates ? buildTrustAllSslContext() : buildDefaultClientSslContext(); + return httpClient.secure(sslSpecification -> { + SslProvider.Builder sslBuilder = sslSpecification.sslContext(sslContext); + handshakeTimeout.ifPresent(timeout -> sslBuilder.handshakeTimeout(timeout)); + }); + } + + private static SslContext buildDefaultClientSslContext() { + try { + return SslContextBuilder.forClient() + .build(); + } catch (SSLException e) { + throw new IllegalStateException(Messages.ERROR_OCCURRED_SETTING_UP_DEFAULT_SSL_CONTEXT, e); + } + } + + private static SslContext buildTrustAllSslContext() { + try { + return SslContextBuilder.forClient() + .trustManager(RestUtil.createDummyTrustManager()) + .build(); + } catch (SSLException e) { + throw new IllegalStateException(Messages.ERROR_OCCURRED_SETTING_UP_ALWAYS_APPROVING_SSL_CONTEXT, e); + } + } + + private static ObjectMapper configureObjectMapper() { + return new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java index d2258ed03c..78e8a2f782 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java @@ -1,24 +1,31 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.CloudFoundryClientFactory; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableCloudFoundryClientFactory; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; -import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; -import org.cloudfoundry.client.CloudFoundryClient; -import org.immutables.value.Value; - import java.net.URL; import java.time.Duration; import java.util.Collections; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; +import org.cloudfoundry.multiapps.controller.client.facade.adapters.LogCacheClient; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; +import org.cloudfoundry.multiapps.controller.client.facade.util.UriUtil; +import org.immutables.value.Value; +import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; @Value.Immutable public abstract class CloudControllerRestClientFactory { + private final RestUtil restUtil = new RestUtil(); + private final Map cachedHttpConnectionPair = new ConcurrentHashMap<>(); + public abstract Optional getSslHandshakeTimeout(); public abstract Optional getConnectTimeout(); @@ -34,38 +41,81 @@ public boolean shouldTrustSelfSignedCertificates() { return false; } - @Value.Derived - public CloudFoundryClientFactory getCloudFoundryClientFactory() { - ImmutableCloudFoundryClientFactory.Builder builder = ImmutableCloudFoundryClientFactory.builder(); - getSslHandshakeTimeout().ifPresent(builder::sslHandshakeTimeout); - getConnectTimeout().ifPresent(builder::connectTimeout); - getConnectionPoolSize().ifPresent(builder::connectionPoolSize); - getThreadPoolSize().ifPresent(builder::threadPoolSize); - getResponseTimeout().ifPresent(builder::responseTimeout); - return builder.build(); - } - public CloudControllerRestClient createClient(URL controllerUrl, CloudCredentials credentials, String organizationName, String spaceName, OAuthClient oAuthClient, Map requestTags) { oAuthClient.init(credentials); - CloudSpaceClient spaceGetter = getCloudFoundryClientFactory().createSpaceClient(controllerUrl, oAuthClient, requestTags); - CloudSpace target = spaceGetter.getSpace(organizationName, spaceName); + CloudSpace target = createSpaceClient(controllerUrl, oAuthClient, requestTags).getSpace(organizationName, spaceName); return createClient(controllerUrl, credentials, target, oAuthClient, requestTags); } public CloudControllerRestClient createClient(URL controllerUrl, CloudCredentials credentials, CloudSpace target) { - return createClient(controllerUrl, credentials, target, createOAuthClient(controllerUrl, credentials.getOrigin()), + return createClient(controllerUrl, credentials, target, createOAuthClient(controllerUrl), Collections.emptyMap()); } public CloudControllerRestClient createClient(URL controllerUrl, CloudCredentials credentials, CloudSpace target, OAuthClient oAuthClient, Map requestTags) { oAuthClient.init(credentials); - CloudFoundryClient delegate = getCloudFoundryClientFactory().createClient(controllerUrl, oAuthClient, requestTags); - return new CloudControllerRestClientImpl(delegate, target); + HttpConnectionPair httpConnectionPair = getCachedHttpConnectionPair(controllerUrl, requestTags); + URL v3ApiUrl = httpConnectionPair.v3ApiUrl(); + URL baseUrl = UriUtil.extractBaseUrl(v3ApiUrl); + + HttpClient httpClient = httpConnectionPair.httpClient(); + CloudControllerV3Client client = buildCloudControllerV3Client(httpClient, baseUrl, oAuthClient, requestTags); + + Function uploadRestClientFactory = uploadTimeout -> { + HttpClient uploadHttpClient = httpClient.responseTimeout(uploadTimeout); + return CloudControllerRestClientBuilder.buildRestClient(uploadHttpClient, baseUrl, oAuthClient, requestTags); + }; + + return new CloudControllerRestClientV3Impl(target, client, uploadRestClientFactory); + } + + public CloudSpaceClient createSpaceClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { + HttpConnectionPair httpConnectionPair = getCachedHttpConnectionPair(controllerUrl, requestTags); + URL baseUrl = UriUtil.extractBaseUrl(httpConnectionPair.v3ApiUrl()); + + CloudControllerV3Client client = buildCloudControllerV3Client(httpConnectionPair.httpClient(), baseUrl, oAuthClient, requestTags); + + return new CloudSpaceClient(client); + } + + private CloudControllerV3Client buildCloudControllerV3Client(HttpClient httpClient, URL baseUrl, OAuthClient oAuthClient, + Map requestTags) { + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpClient, baseUrl, oAuthClient, requestTags); + WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(httpClient, baseUrl, oAuthClient, requestTags); + + return new CloudControllerV3Client(restClient, webClient); + } + + public LogCacheClient createLogCacheClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { + HttpConnectionPair httpConnectionPair = getCachedHttpConnectionPair(controllerUrl, requestTags); + + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpConnectionPair.httpClient(), + httpConnectionPair.logCacheUrl(), + oAuthClient, requestTags); + return new LogCacheClient(restClient); + } + + private HttpConnectionPair getCachedHttpConnectionPair(URL controllerUrl, Map requestTags) { + return cachedHttpConnectionPair.computeIfAbsent(controllerUrl.getHost(), _ -> { + HttpClient httpClient = CloudControllerRestClientBuilder.buildHttpClient(getClientConfigurationOptions()); + Map rootDocumentLinks = CloudControllerRootDocumentClient.getRootDocumentLinks(controllerUrl, requestTags); + + URL v3ApiUrl = UriUtil.resolveV3ApiUrl(controllerUrl, rootDocumentLinks); + URL logCacheUrl = UriUtil.resolveLogCacheUrl(controllerUrl, rootDocumentLinks); + + return new HttpConnectionPair(httpClient, v3ApiUrl, logCacheUrl); + }); + } + + private ClientConfigurationOptions getClientConfigurationOptions() { + return new ClientConfigurationOptions(getConnectTimeout(), getResponseTimeout(), getSslHandshakeTimeout(), + getConnectionPoolSize(), getThreadPoolSize(), + shouldTrustSelfSignedCertificates()); } - private OAuthClient createOAuthClient(URL controllerUrl, String origin) { + private OAuthClient createOAuthClient(URL controllerUrl) { return restUtil.createOAuthClientByControllerUrl(controllerUrl, shouldTrustSelfSignedCertificates()); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java deleted file mode 100644 index c1e6718d16..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java +++ /dev/null @@ -1,2621 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.rest; - -import java.nio.file.Path; -import java.text.MessageFormat; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import org.cloudfoundry.AbstractCloudFoundryException; -import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.DockerData; -import org.cloudfoundry.client.v3.Lifecycle; -import org.cloudfoundry.client.v3.LifecycleType; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.Resource; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.applications.Application; -import org.cloudfoundry.client.v3.applications.ApplicationFeature; -import org.cloudfoundry.client.v3.applications.ApplicationRelationships; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.cloudfoundry.client.v3.applications.CreateApplicationRequest; -import org.cloudfoundry.client.v3.applications.CreateApplicationResponse; -import org.cloudfoundry.client.v3.applications.DeleteApplicationRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationCurrentDropletRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationCurrentDropletResponse; -import org.cloudfoundry.client.v3.applications.GetApplicationEnvironmentVariablesRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationEnvironmentVariablesResponse; -import org.cloudfoundry.client.v3.applications.GetApplicationProcessRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationProcessResponse; -import org.cloudfoundry.client.v3.applications.GetApplicationProcessStatisticsRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationProcessStatisticsResponse; -import org.cloudfoundry.client.v3.applications.GetApplicationRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationSshEnabledRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationSshEnabledResponse; -import org.cloudfoundry.client.v3.applications.ListApplicationBuildsRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationFeaturesRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationPackagesRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationRoutesRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationsRequest; -import org.cloudfoundry.client.v3.applications.ScaleApplicationRequest; -import org.cloudfoundry.client.v3.applications.SetApplicationCurrentDropletRequest; -import org.cloudfoundry.client.v3.applications.StartApplicationRequest; -import org.cloudfoundry.client.v3.applications.StopApplicationRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationEnvironmentVariablesRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationFeatureRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationResponse; -import org.cloudfoundry.client.v3.auditevents.AuditEventResource; -import org.cloudfoundry.client.v3.auditevents.ListAuditEventsRequest; -import org.cloudfoundry.client.v3.builds.Build; -import org.cloudfoundry.client.v3.builds.CreateBuildRequest; -import org.cloudfoundry.client.v3.builds.GetBuildRequest; -import org.cloudfoundry.client.v3.builds.ListBuildsRequest; -import org.cloudfoundry.client.v3.domains.CreateDomainRequest; -import org.cloudfoundry.client.v3.domains.DeleteDomainRequest; -import org.cloudfoundry.client.v3.domains.Domain; -import org.cloudfoundry.client.v3.domains.DomainRelationships; -import org.cloudfoundry.client.v3.domains.DomainResource; -import org.cloudfoundry.client.v3.domains.ListDomainsRequest; -import org.cloudfoundry.client.v3.jobs.GetJobRequest; -import org.cloudfoundry.client.v3.organizations.GetOrganizationDefaultDomainRequest; -import org.cloudfoundry.client.v3.organizations.ListOrganizationDomainsRequest; -import org.cloudfoundry.client.v3.packages.CreatePackageRequest; -import org.cloudfoundry.client.v3.packages.CreatePackageResponse; -import org.cloudfoundry.client.v3.packages.GetPackageRequest; -import org.cloudfoundry.client.v3.packages.Package; -import org.cloudfoundry.client.v3.packages.PackageRelationships; -import org.cloudfoundry.client.v3.packages.PackageResource; -import org.cloudfoundry.client.v3.packages.PackageType; -import org.cloudfoundry.client.v3.packages.UploadPackageRequest; -import org.cloudfoundry.client.v3.processes.Data; -import org.cloudfoundry.client.v3.processes.HealthCheck; -import org.cloudfoundry.client.v3.processes.HealthCheckType; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheck; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.cloudfoundry.client.v3.processes.UpdateProcessRequest; -import org.cloudfoundry.client.v3.roles.ListRolesRequest; -import org.cloudfoundry.client.v3.roles.RoleResource; -import org.cloudfoundry.client.v3.roles.RoleType; -import org.cloudfoundry.client.v3.routes.CreateRouteRequest; -import org.cloudfoundry.client.v3.routes.CreateRouteResponse; -import org.cloudfoundry.client.v3.routes.DeleteRouteRequest; -import org.cloudfoundry.client.v3.routes.Destination; -import org.cloudfoundry.client.v3.routes.InsertRouteDestinationsRequest; -import org.cloudfoundry.client.v3.routes.ListRoutesRequest; -import org.cloudfoundry.client.v3.routes.RemoveRouteDestinationsRequest; -import org.cloudfoundry.client.v3.routes.RouteRelationships; -import org.cloudfoundry.client.v3.routes.RouteResource; -import org.cloudfoundry.client.v3.servicebindings.CreateServiceBindingRequest; -import org.cloudfoundry.client.v3.servicebindings.CreateServiceBindingResponse; -import org.cloudfoundry.client.v3.servicebindings.DeleteServiceBindingRequest; -import org.cloudfoundry.client.v3.servicebindings.GetServiceBindingDetailsRequest; -import org.cloudfoundry.client.v3.servicebindings.GetServiceBindingDetailsResponse; -import org.cloudfoundry.client.v3.servicebindings.GetServiceBindingParametersRequest; -import org.cloudfoundry.client.v3.servicebindings.GetServiceBindingParametersResponse; -import org.cloudfoundry.client.v3.servicebindings.ListServiceBindingsRequest; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingRelationships; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingType; -import org.cloudfoundry.client.v3.servicebindings.UpdateServiceBindingRequest; -import org.cloudfoundry.client.v3.servicebrokers.BasicAuthentication; -import org.cloudfoundry.client.v3.servicebrokers.CreateServiceBrokerRequest; -import org.cloudfoundry.client.v3.servicebrokers.DeleteServiceBrokerRequest; -import org.cloudfoundry.client.v3.servicebrokers.ListServiceBrokersRequest; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerRelationships; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerResource; -import org.cloudfoundry.client.v3.servicebrokers.UpdateServiceBrokerRequest; -import org.cloudfoundry.client.v3.serviceinstances.CreateServiceInstanceRequest; -import org.cloudfoundry.client.v3.serviceinstances.DeleteServiceInstanceRequest; -import org.cloudfoundry.client.v3.serviceinstances.GetManagedServiceParametersRequest; -import org.cloudfoundry.client.v3.serviceinstances.GetManagedServiceParametersResponse; -import org.cloudfoundry.client.v3.serviceinstances.GetServiceInstanceRequest; -import org.cloudfoundry.client.v3.serviceinstances.GetUserProvidedCredentialsRequest; -import org.cloudfoundry.client.v3.serviceinstances.GetUserProvidedCredentialsResponse; -import org.cloudfoundry.client.v3.serviceinstances.ListServiceInstancesRequest; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstance; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceRelationships; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceResource; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; -import org.cloudfoundry.client.v3.serviceinstances.UpdateServiceInstanceRequest; -import org.cloudfoundry.client.v3.serviceofferings.GetServiceOfferingRequest; -import org.cloudfoundry.client.v3.serviceofferings.ListServiceOfferingsRequest; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOffering; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingResource; -import org.cloudfoundry.client.v3.serviceplans.GetServicePlanRequest; -import org.cloudfoundry.client.v3.serviceplans.ListServicePlansRequest; -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.cloudfoundry.client.v3.serviceplans.ServicePlanResource; -import org.cloudfoundry.client.v3.serviceplans.UpdateServicePlanVisibilityRequest; -import org.cloudfoundry.client.v3.serviceplans.Visibility; -import org.cloudfoundry.client.v3.spaces.DeleteUnmappedRoutesRequest; -import org.cloudfoundry.client.v3.stacks.ListStacksRequest; -import org.cloudfoundry.client.v3.stacks.Stack; -import org.cloudfoundry.client.v3.tasks.CancelTaskRequest; -import org.cloudfoundry.client.v3.tasks.CreateTaskRequest; -import org.cloudfoundry.client.v3.tasks.GetTaskRequest; -import org.cloudfoundry.client.v3.tasks.ListTasksRequest; -import org.cloudfoundry.client.v3.tasks.Task; -import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.Constants; -import org.cloudfoundry.multiapps.controller.client.facade.Messages; -import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudAsyncJob; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudProcess; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceBroker; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudStack; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudTask; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawInstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawUserRole; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawV3CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerCredentials; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ErrorDetails; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableErrorDetails; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableUpload; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; -import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; -import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; -import org.cloudfoundry.multiapps.controller.client.facade.util.JobV3Util; -import org.cloudfoundry.util.PaginationUtils; -import org.springframework.http.HttpStatus; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Abstract implementation of the CloudControllerClient intended to serve as the base. - */ -public class CloudControllerRestClientImpl implements CloudControllerRestClient { - - private static final long PACKAGE_UPLOAD_JOB_POLLING_PERIOD = TimeUnit.SECONDS.toMillis(5); - private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); - private static final Duration BINDING_OPERATIONS_TIMEOUT = Duration.ofMinutes(10); - private static final int MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST = 4000; - - private CloudSpace target; // optional, as some operations do not require a targeted space - private CloudFoundryClient delegate; - - /** - * Only for unit tests. This works around the fact that the initialize method is called within the constructor and hence can not be - * overloaded, making it impossible to write unit tests that don't trigger network calls. - */ - protected CloudControllerRestClientImpl() { - } - - public CloudControllerRestClientImpl(CloudFoundryClient delegate) { - this(delegate, null); - } - - public CloudControllerRestClientImpl(CloudFoundryClient delegate, CloudSpace target) { - this.target = target; - this.delegate = delegate; - } - - @Override - public CloudSpace getTarget() { - return target; - } - - @Override - public void addDomain(String domainName) { - assertSpaceProvided("add domain"); - CloudDomain domain = findDomainByName(domainName); - if (domain == null) { - doCreateDomain(domainName); - } - } - - @Override - public void addRoute(String host, String domainName, String path) { - assertSpaceProvided("add route for domain"); - UUID domainGuid = getRequiredDomainGuid(domainName); - doAddRoute(domainGuid, host, path); - } - - @Override - public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName) { - return bindServiceInstance(bindingName, applicationName, serviceInstanceName, null); - } - - @Override - public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, - Map parameters) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); - - var createBindingRequest = CreateServiceBindingRequest.builder() - .name(bindingName) - .type(ServiceBindingType.APPLICATION) - .relationships(ServiceBindingRelationships.builder() - .application(buildToOneRelationship( - applicationGuid)) - .serviceInstance( - buildToOneRelationship( - serviceInstanceGuid)) - .build()); - if (!CollectionUtils.isEmpty(parameters)) { - createBindingRequest.parameters(parameters); - } - return delegate.serviceBindingsV3() - .create(createBindingRequest.build()) - .map(CreateServiceBindingResponse::getJobId) - .block(); - } - - @Override - public void createApplication(ApplicationToCreateDto applicationToCreateDto) { - assertSpaceProvided("create application"); - CreateApplicationRequest createApplicationRequest = CreateApplicationRequest.builder() - .name(applicationToCreateDto.getName()) - .metadata(applicationToCreateDto.getMetadata()) - .lifecycle(buildApplicationLifecycle( - applicationToCreateDto.getStaging())) - .relationships(buildApplicationRelationships()) - .environmentVariables(applicationToCreateDto.getEnv()) - .build(); - CreateApplicationResponse createApplicationResponse = delegate.applicationsV3() - .create(createApplicationRequest) - .block(); - updateApplicationAttributes(applicationToCreateDto, createApplicationResponse.getId()); - } - - private Lifecycle buildApplicationLifecycle(Staging staging) { - // Prioritize Docker lifecycle if DockerInfo is provided - if (staging.getDockerInfo() != null) { - return createDockerLifecycle(); - } - - // Determine lifecycle type, defaulting to BUILDPACK if lifecycleType is null - LifecycleType lifecycleType = staging.getLifecycleType() != null ? LifecycleType.valueOf(staging.getLifecycleType() - .name()) : LifecycleType.BUILDPACK; - - return createLifecycleByType(staging, lifecycleType); - } - - private Lifecycle createLifecycleByType(Staging staging, LifecycleType lifecycleType) { - validateLifecycleConfiguration(staging, lifecycleType); - BuildpackData buildpackData = createBuildpackData(staging); - return Lifecycle.builder() - .type(lifecycleType) - .data(buildpackData) - .build(); - } - - private Lifecycle createDockerLifecycle() { - return Lifecycle.builder() - .type(LifecycleType.DOCKER) - .data(DockerData.builder() - .build()) - .build(); - } - - private void validateLifecycleConfiguration(Staging staging, LifecycleType lifecycleType) { - if (lifecycleType == LifecycleType.CNB && CollectionUtils.isEmpty(staging.getBuildpacks())) { - throw new IllegalArgumentException(Messages.BUILDPACKS_ARE_REQUIRED_FOR_CNB_LIFECYCLE_TYPE); - } - } - - private BuildpackData createBuildpackData(Staging staging) { - BuildpackData.Builder buildpackDataBuilder = BuildpackData.builder() - .stack(staging.getStackName()); - if (staging.getBuildpacks() != null) { - buildpackDataBuilder.addAllBuildpacks(staging.getBuildpacks()); - } - return buildpackDataBuilder.build(); - } - - private ApplicationRelationships buildApplicationRelationships() { - return ApplicationRelationships.builder() - .space(buildToOneRelationship(getTargetSpaceGuid())) - .build(); - } - - private void updateApplicationAttributes(ApplicationToCreateDto applicationToCreateDto, String applicationGuid) { - UUID createdApplicationGuid = UUID.fromString(applicationGuid); - updateApplicationProcess(createdApplicationGuid, applicationToCreateDto.getStaging()); - delegate.applicationsV3() - .scale(ScaleApplicationRequest.builder() - .applicationId(createdApplicationGuid.toString()) - .type("web") - .memoryInMb(applicationToCreateDto.getMemoryInMb()) - .diskInMb(applicationToCreateDto.getDiskQuotaInMb()) - .build()) - .block(); - if (!CollectionUtils.isEmpty(applicationToCreateDto.getRoutes())) { - addRoutes(applicationToCreateDto.getRoutes(), createdApplicationGuid); - } - } - - private void updateApplicationProcess(UUID applicationGuid, Staging staging) { - staging.getAppFeatures() - .entrySet() - .forEach(entry -> updateAppFeature(applicationGuid, entry.getKey(), entry.getValue())); - GetApplicationProcessResponse getApplicationProcessResponse = getApplicationProcessResource(applicationGuid); - UpdateProcessRequest.Builder updateProcessRequestBuilder = UpdateProcessRequest.builder() - .processId(getApplicationProcessResponse.getId()) - .command(staging.getCommand()); - if (staging.getHealthCheckType() != null) { - updateProcessRequestBuilder.healthCheck(buildHealthCheck(staging)); - } - if (staging.getReadinessHealthCheckType() != null) { - updateProcessRequestBuilder.readinessHealthCheck(buildReadinessHealthCheck(staging)); - } - delegate.processes() - .update(updateProcessRequestBuilder.build()) - .block(); - } - - private ReadinessHealthCheck buildReadinessHealthCheck(Staging staging) { - return ReadinessHealthCheck.builder() - .type(ReadinessHealthCheckType.from(staging.getReadinessHealthCheckType())) - .data(buildReadinessHealthCheckData(staging)) - .build(); - } - - private Data buildReadinessHealthCheckData(Staging staging) { - return Data.builder() - .invocationTimeout(staging.getReadinessHealthCheckInvocationTimeout()) - .endpoint(staging.getReadinessHealthCheckHttpEndpoint()) - .interval(staging.getReadinessHealthCheckInterval()) - .build(); - } - - private void updateAppFeature(UUID applicationGuid, String featureName, boolean enabled) { - delegate.applicationsV3() - .updateFeature(UpdateApplicationFeatureRequest.builder() - .featureName(featureName) - .enabled(enabled) - .applicationId(applicationGuid.toString()) - .build()) - .block(); - } - - private GetApplicationProcessResponse getApplicationProcessResource(UUID applicationGuid) { - return delegate.applicationsV3() - .getProcess(GetApplicationProcessRequest.builder() - .type("web") - .applicationId(applicationGuid.toString()) - .build()) - .block(); - } - - private HealthCheck buildHealthCheck(Staging staging) { - HealthCheckType healthCheckType = HealthCheckType.from(staging.getHealthCheckType()); - return HealthCheck.builder() - .type(healthCheckType) - .data(Data.builder() - .endpoint(staging.getHealthCheckHttpEndpoint()) - .timeout(staging.getHealthCheckTimeout()) - .invocationTimeout(staging.getInvocationTimeout()) - .interval(staging.getHealthCheckInterval()) - .build()) - .build(); - } - - private void addRoutes(Set routes, UUID applicationGuid) { - Map domains = getDomainsFromRoutes(routes); - for (CloudRoute route : routes) { - validateDomainForRoute(route, domains); - UUID domainGuid = domains.get(route.getDomain() - .getName()); - UUID routeGuid = getOrAddRoute(domainGuid, route.getHost(), route.getPath()); - bindRoute(routeGuid, applicationGuid, route.getRequestedProtocol()); - } - } - - @Override - public void createServiceInstance(CloudServiceInstance serviceInstance) { - assertSpaceProvided("create service instance"); - Assert.notNull(serviceInstance, "Service instance must not be null."); - CloudServicePlan servicePlan = findPlanForService(serviceInstance, serviceInstance.getPlan()); - UUID servicePlanGuid = servicePlan.getGuid(); - - delegate.serviceInstancesV3() - .create(CreateServiceInstanceRequest.builder() - .type(ServiceInstanceType.MANAGED) - .name(serviceInstance.getName()) - .metadata(serviceInstance.getV3Metadata()) - .relationships(ServiceInstanceRelationships.builder() - .servicePlan(buildToOneRelationship( - servicePlanGuid.toString())) - .space(buildToOneRelationship( - getTargetSpaceGuid().toString())) - .build()) - .tags(serviceInstance.getTags()) - .parameters(serviceInstance.getCredentials()) - .build()) - .block(); - } - - @Override - public String createServiceBroker(CloudServiceBroker serviceBroker) { - Assert.notNull(serviceBroker, "Service broker must not be null."); - - ServiceBrokerRelationships serviceBrokerRelationship = Optional.ofNullable(serviceBroker.getSpaceGuid()) - .map(UUID::fromString) - .map(this::buildToOneRelationship) - .map(spaceRelationship -> ServiceBrokerRelationships.builder() - .space( - spaceRelationship) - .build()) - .orElse(null); - - return delegate.serviceBrokersV3() - .create(CreateServiceBrokerRequest.builder() - .name(serviceBroker.getName()) - .url(serviceBroker.getUrl()) - .authentication(BasicAuthentication.builder() - .username(serviceBroker.getUsername()) - .password(serviceBroker.getPassword()) - .build()) - .relationships(serviceBrokerRelationship) - .build()) - .block(); - } - - @Override - public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - doCreateServiceKeySync(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), serviceInstance); - - return fetchWithAuxiliaryContent( - () -> getServiceKeyResourceByNameAndServiceInstanceGuid(keyModel.getName(), getGuid(serviceInstance)), - fetchedKey -> zipWithAuxiliaryServiceKeyContent(fetchedKey, serviceInstance)); - } - - @Override - public Optional createServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return doCreateServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), serviceInstance); - } - - @Override - public Optional createServiceKey(String serviceInstanceName, String serviceKeyName, Map parameters) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return doCreateServiceKey(serviceKeyName, parameters, null, serviceInstance); - } - - private void doCreateServiceKeySync(String name, Map parameters, Metadata metadata, - CloudServiceInstance serviceInstance) { - CreateServiceBindingRequest createServiceBindingRequest = buildServiceCredentialBindingRequest(name, parameters, metadata, - serviceInstance); - delegate.serviceBindingsV3() - .create(createServiceBindingRequest) - .map(response -> response.getJobId() - .get()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, BINDING_OPERATIONS_TIMEOUT, jobId)) - .block(); - } - - private Optional doCreateServiceKey(String name, Map parameters, Metadata metadata, - CloudServiceInstance serviceInstance) { - CreateServiceBindingRequest createServiceBindingRequest = buildServiceCredentialBindingRequest(name, parameters, metadata, - serviceInstance); - return delegate.serviceBindingsV3() - .create(createServiceBindingRequest) - .map(CreateServiceBindingResponse::getJobId) - .block(); - } - - private CreateServiceBindingRequest buildServiceCredentialBindingRequest(String name, Map parameters, Metadata metadata, - CloudServiceInstance serviceInstance) { - if (serviceInstance.getType() != ServiceInstanceType.MANAGED) { - throw new IllegalArgumentException( - String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, serviceInstance.getName())); - } - UUID serviceInstanceGuid = getGuid(serviceInstance); - - var createBindingRequest = CreateServiceBindingRequest.builder() - .type(ServiceBindingType.KEY) - .name(name) - .metadata(metadata) - .relationships(ServiceBindingRelationships.builder() - .serviceInstance( - buildToOneRelationship( - serviceInstanceGuid)) - .build()); - if (!CollectionUtils.isEmpty(parameters)) { - createBindingRequest.parameters(parameters); - } - return createBindingRequest.build(); - } - - @Override - public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { - assertSpaceProvided("create service instance"); - Assert.notNull(serviceInstance, "Service instance must not be null."); - String syslogDrainUrl = StringUtils.hasText(serviceInstance.getSyslogDrainUrl()) ? serviceInstance.getSyslogDrainUrl() : ""; - delegate.serviceInstancesV3() - .create(CreateServiceInstanceRequest.builder() - .name(serviceInstance.getName()) - .metadata(serviceInstance.getV3Metadata()) - .type(ServiceInstanceType.USER_PROVIDED) - .credentials(serviceInstance.getCredentials()) - .syslogDrainUrl(syslogDrainUrl) - .tags(serviceInstance.getTags()) - .relationships(ServiceInstanceRelationships.builder() - .space(buildToOneRelationship( - getTargetSpaceGuid().toString())) - .build()) - .build()) - .block(); - } - - @Override - public void deleteApplication(String applicationName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - deleteApplication(applicationGuid); - } - - private void deleteApplication(UUID applicationGuid) { - delegate.applicationsV3() - .delete(DeleteApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, DELETE_JOB_TIMEOUT, jobId)) - .block(); - } - - @Override - public void deleteDomain(String domainName) { - assertSpaceProvided("delete domain"); - CloudDomain domain = findDomainByName(domainName, true); - List routes = findRoutes(domain); - if (!routes.isEmpty()) { - throw new IllegalStateException("Unable to remove domain that is in use --" + " it has " + routes.size() + " routes."); - } - doDeleteDomain(getGuid(domain)); - } - - /** - * Delete routes that do not have any application which is assigned to them. - */ - @Override - public void deleteOrphanedRoutes() { - delegate.spacesV3() - .deleteUnmappedRoutes(DeleteUnmappedRoutesRequest.builder() - .spaceId(getTargetSpaceGuid().toString()) - .build()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, DELETE_JOB_TIMEOUT, jobId)) - .block(); - } - - @Override - public void deleteRoute(String host, String domainName, String path) { - assertSpaceProvided("delete route for domain"); - UUID routeGuid = getRouteGuid(getRequiredDomainGuid(domainName), host, path); - if (routeGuid == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Host " + host + " not found for domain " + domainName + "."); - } - doDeleteRoute(routeGuid); - } - - @Override - public void deleteServiceInstance(String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); - doDeleteServiceInstance(serviceInstance.getGuid()); - } - - @Override - public void deleteServiceInstance(CloudServiceInstance serviceInstance) { - doDeleteServiceInstance(serviceInstance.getGuid()); - } - - @Override - public String deleteServiceBroker(String name) { - CloudServiceBroker broker = getServiceBroker(name); - UUID guid = broker.getMetadata() - .getGuid(); - return delegate.serviceBrokersV3() - .delete(DeleteServiceBrokerRequest.builder() - .serviceBrokerId(guid.toString()) - .build()) - .block(); - } - - @Override - public Optional deleteServiceBinding(String serviceInstanceName, String serviceKeyName) { - CloudServiceKey serviceKey = getServiceKey(serviceInstanceName, serviceKeyName); - if (serviceKey == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service key " + serviceKeyName + " not found."); - } - return doDeleteServiceBindings(List.of(serviceKey.getGuid())).stream() - .findFirst(); - } - - @Override - public CloudApplication getApplication(String applicationName) { - return getApplication(applicationName, true); - } - - @Override - public CloudApplication getApplication(String applicationName, boolean required) { - return findApplicationByName(applicationName, required); - } - - @Override - public UUID getApplicationGuid(String applicationName) { - return getRequiredApplicationGuid(applicationName); - } - - @Override - public String getApplicationName(UUID applicationGuid) { - // This will throw a CloudOperationException with a 404 if no app with given GUID is present - return getApplicationByGuid(applicationGuid).block() - .getName(); - } - - @Override - public Map getApplicationEnvironment(UUID applicationGuid) { - return delegate.applicationsV3() - .getEnvironmentVariables(GetApplicationEnvironmentVariablesRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .map(GetApplicationEnvironmentVariablesResponse::getVars) - .block(); - } - - @Override - public Map getApplicationEnvironment(String applicationName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - return getApplicationEnvironment(applicationGuid); - } - - @Override - public List getApplicationEvents(String applicationName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - return getEventsByTarget(applicationGuid); - } - - @Override - public List getEventsByTarget(UUID uuid) { - return findEventsByTarget(uuid.toString()); - } - - @Override - public InstancesInfo getApplicationInstances(CloudApplication application) { - if (application.getState() - .equals(CloudApplication.State.STARTED)) { - return findApplicationInstances(getGuid(application)); - } - return ImmutableInstancesInfo.builder() - .instances(Collections.emptyList()) - .build(); - } - - @Override - public InstancesInfo getApplicationInstances(UUID applicationGuid) { - return findApplicationInstances(applicationGuid); - } - - @Override - public CloudProcess getApplicationProcess(UUID applicationGuid) { - return fetch(() -> Mono.just(getApplicationProcessResource(applicationGuid)), ImmutableRawCloudProcess::of); - } - - @Override - public List getApplicationRoutes(UUID applicationGuid) { - return fetchList(() -> getRouteResourcesByAppGuid(applicationGuid), routeResource -> ImmutableRawCloudRoute.builder() - .route(routeResource) - .applicationGuid( - applicationGuid) - .build()); - } - - @Override - public boolean getApplicationSshEnabled(UUID applicationGuid) { - return delegate.applicationsV3() - .getSshEnabled(GetApplicationSshEnabledRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .map(GetApplicationSshEnabledResponse::getEnabled) - .defaultIfEmpty(false) - .block(); - } - - @Override - public Map getApplicationFeatures(UUID applicationGuid) { - IntFunction pageRequestSupplier = page -> ListApplicationFeaturesRequest.builder() - .applicationId( - applicationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .listFeatures(pageRequestSupplier.apply(page))) - .collect(Collectors.toMap(ApplicationFeature::getName, ApplicationFeature::getEnabled)) - .block(); - } - - @Override - public List getApplications() { - return fetchList(this::getApplicationResources, application -> ImmutableRawCloudApplication.builder() - .application(application) - .space(target) - .build()); - } - - @Override - public List getApplicationsByMetadataLabelSelector(String labelSelector) { - assertSpaceProvided("get applications"); - return fetchList(() -> getApplicationsByLabelSelector(labelSelector), application -> ImmutableRawCloudApplication.builder() - .application( - application) - .space(target) - .build()); - } - - private Flux getApplicationsByLabelSelector(String labelSelector) { - IntFunction pageRequestSupplier = page -> ListApplicationsRequest.builder() - .spaceId(getTargetSpaceGuid().toString()) - .labelSelector(labelSelector) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .list(pageRequestSupplier.apply(page))); - } - - @Override - public CloudDomain getDefaultDomain() { - return fetch(() -> getDefaultDomainResource(getTargetOrganizationGuid().toString()), ImmutableRawCloudDomain::of); - } - - private Mono getDefaultDomainResource(String guid) { - return delegate.organizationsV3() - .getDefaultDomain(GetOrganizationDefaultDomainRequest.builder() - .organizationId(guid) - .build()); - } - - @Override - public List getSharedDomains() { - return fetchList(this::getSharedDomainResources, ImmutableRawCloudDomain::of); - } - - @Override - public List getDomains() { - return fetchList(this::getDomainResources, ImmutableRawCloudDomain::of); - } - - @Override - public List getDomainsForOrganization() { - assertSpaceProvided("access organization domains"); - return findDomainsByOrganizationGuid(getTargetOrganizationGuid()); - } - - @Override - public List getPrivateDomains() { - return fetchList(this::getPrivateDomainResources, ImmutableRawCloudDomain::of); - } - - @Override - public List getEvents() { - return fetchList(this::getEventResources, ImmutableRawCloudEvent::of); - } - - @Override - public List getRoutes(String domainName) { - assertSpaceProvided("get routes for domain"); - CloudDomain domain = findDomainByName(domainName, true); - return findRoutes(domain); - } - - @Override - public UUID getRequiredServiceInstanceGuid(String name) { - Resource serviceInstanceResource = getServiceInstanceByName(name).block(); - if (serviceInstanceResource == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + name + " not found."); - } - return UUID.fromString(serviceInstanceResource.getId()); - } - - @Override - public CloudServiceInstance getServiceInstance(String serviceInstanceName) { - return getServiceInstance(serviceInstanceName, true); - } - - @Override - public CloudServiceInstance getServiceInstance(String serviceInstanceName, boolean required) { - CloudServiceInstance serviceInstance = findServiceInstanceByName(serviceInstanceName); - return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); - } - - private CloudServiceInstance getServiceInstanceIfRequired(String serviceInstanceName, CloudServiceInstance serviceInstance, - boolean required) { - if (serviceInstance == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + serviceInstanceName + " not found."); - } - return serviceInstance; - } - - @Override - public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { - // TODO: Refactor and use serviceBindingsV3.get() when https://github.com/cloudfoundry/cf-java-client/pull/1171 is merged and - // released - return fetch(() -> getServiceBindingResource(serviceBindingGuid), ImmutableRawCloudServiceBinding::of); - } - - private Mono getServiceBindingResource(UUID serviceBindingGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .page(page) - .id(serviceBindingGuid.toString()) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - @Override - public List getServiceAppBindings(UUID serviceInstanceGuid) { - return fetchList(() -> getServiceBindingResourcesByServiceInstanceGuid(serviceInstanceGuid), ImmutableRawCloudServiceBinding::of); - } - - @Override - public List getAppBindings(UUID applicationGuid) { - return fetchList(() -> getServiceBindingResourcesByApplicationGuid(applicationGuid), ImmutableRawCloudServiceBinding::of); - } - - @Override - public List getServiceBindingsForApplication(UUID applicationId, UUID serviceInstanceGuid) { - return fetchList(() -> getServiceBindingResourcesByApplicationGuidAndServiceInstanceGuid(applicationId, serviceInstanceGuid), - ImmutableRawCloudServiceBinding::of); - } - - @Override - public CloudServiceBroker getServiceBroker(String name) { - return getServiceBroker(name, true); - } - - @Override - public CloudServiceBroker getServiceBroker(String name, boolean required) { - CloudServiceBroker serviceBroker = findServiceBrokerByName(name); - if (serviceBroker == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service broker " + name + " not found."); - } - return serviceBroker; - } - - @Override - public List getServiceBrokers() { - return fetchList(this::getServiceBrokerResources, ImmutableRawCloudServiceBroker::of); - } - - @Override - public CloudServiceKey getServiceKey(String serviceInstanceName, String serviceKeyName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return fetchWithAuxiliaryContent(() -> getServiceKeyResourceByNameAndServiceInstanceGuid(serviceKeyName, serviceInstance.getGuid()), - serviceKey -> zipWithAuxiliaryServiceKeyContent(serviceKey, serviceInstance)); - } - - @Override - public List getServiceKeys(String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return getServiceKeys(serviceInstance); - } - - @Override - public List getServiceKeysWithCredentials(String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return getServiceKeysWithCredentials(serviceInstance); - } - - @Override - public List getServiceKeys(CloudServiceInstance serviceInstance) { - return fetchList(() -> getServiceKeyResource(serviceInstance), serviceKey -> ImmutableRawCloudServiceKey.builder() - .serviceInstance( - serviceInstance) - .serviceBindingResource( - serviceKey) - .build()); - } - - @Override - public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) { - return fetchListWithAuxiliaryContent(() -> getServiceKeyResource(serviceInstance), - serviceKey -> zipWithAuxiliaryServiceKeyContent(serviceKey, serviceInstance)); - } - - private Mono> zipWithAuxiliaryServiceKeyContent(ServiceBindingResource key, - CloudServiceInstance serviceInstance) { - return getServiceKeyCredentials(key.getId()).map(credentials -> ImmutableRawCloudServiceKey.builder() - .serviceBindingResource(key) - .credentials(credentials) - .serviceInstance(serviceInstance) - .build()); - } - - private Mono> getServiceKeyCredentials(String keyGuid) { - return delegate.serviceBindingsV3() - .getDetails(GetServiceBindingDetailsRequest.builder() - .serviceBindingId(keyGuid) - .build()) - // CF V3 API returns 404 when fetching credentials of a service key which creation failed - .onErrorResume(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), t -> Mono.just( - GetServiceBindingDetailsResponse.builder() - .volumeMounts(Collections.emptyList()) - .credentials(Collections.emptyMap()) - .build())) - .map(GetServiceBindingDetailsResponse::getCredentials); - } - - @Override - public Map getServiceInstanceParameters(UUID guid) { - return delegate.serviceInstancesV3() - .getManagedServiceParameters(GetManagedServiceParametersRequest.builder() - .serviceInstanceId(guid.toString()) - .build()) - .map(GetManagedServiceParametersResponse::getParameters) - .block(); - } - - @Override - public Map getUserProvidedServiceInstanceParameters(UUID guid) { - return delegate.serviceInstancesV3() - .getUserProvidedCredentials(GetUserProvidedCredentialsRequest.builder() - .serviceInstanceId(guid.toString()) - .build()) - .map(GetUserProvidedCredentialsResponse::getCredentials) - .block(); - } - - @Override - public Map getServiceBindingParameters(UUID guid) { - return delegate.serviceBindingsV3() - .getParameters(GetServiceBindingParametersRequest.builder() - .serviceBindingId(guid.toString()) - .build()) - .map(GetServiceBindingParametersResponse::getParameters) - .block(); - } - - @Override - public List getServiceOfferings() { - return fetchListWithAuxiliaryContent(this::getServiceResources, this::zipWithAuxiliaryServiceOfferingContent); - } - - @Override - public void updateServicePlan(String serviceName, String planName) { - CloudServiceInstance service = getServiceInstance(serviceName); - if (service.isUserProvided()) { - return; - } - CloudServicePlan plan = findPlanForService(service, planName); - delegate.serviceInstancesV3() - .update(UpdateServiceInstanceRequest.builder() - .serviceInstanceId(service.getGuid() - .toString()) - .relationships(ServiceInstanceRelationships.builder() - .servicePlan( - buildToOneRelationship(plan.getGuid())) - .build()) - .build()) - .block(); - } - - @Override - public String getServiceInstanceName(UUID serviceInstanceGuid) { - return getServiceInstanceByGuid(serviceInstanceGuid).block() - .getName(); - } - - @Override - public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName) { - return getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName, true); - } - - @Override - public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName, boolean required) { - CloudServiceInstance serviceInstance = fetch(() -> getServiceInstanceResourceByName(serviceInstanceName), - ImmutableRawCloudServiceInstance::of); - return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); - } - - @Override - public void updateServiceParameters(String serviceName, Map parameters) { - CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); - var updateServiceInstanceRequest = UpdateServiceInstanceRequest.builder() - .serviceInstanceId(service.getGuid() - .toString()); - if (service.isUserProvided()) { - updateServiceInstanceRequest.credentials(parameters); - } else { - updateServiceInstanceRequest.parameters(parameters); - } - delegate.serviceInstancesV3() - .update(updateServiceInstanceRequest.build()) - .block(); - } - - @Override - public void updateServiceTags(String serviceName, List tags) { - UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceName); - delegate.serviceInstancesV3() - .update(UpdateServiceInstanceRequest.builder() - .serviceInstanceId(serviceInstanceGuid.toString()) - .tags(tags) - .build()) - .block(); - } - - @Override - public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) { - CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); - if (!service.isUserProvided()) { - return; - } - String updatedSyslogDrain = StringUtils.hasText(syslogDrainUrl) ? syslogDrainUrl : ""; - delegate.serviceInstancesV3() - .update(UpdateServiceInstanceRequest.builder() - .serviceInstanceId(service.getGuid() - .toString()) - .syslogDrainUrl(updatedSyslogDrain) - .build()) - .block(); - } - - private List> toBatches(Collection largeList, int maxCharLength) { - if (largeList.isEmpty()) { - return Collections.emptyList(); - } - - List> batches = new ArrayList<>(); - int currentBatchLength = 0, currentBatchIndex = 0; - batches.add(new ArrayList<>()); - - for (T element : largeList) { - int elementLength = element.toString() - .length(); - if (elementLength + currentBatchLength >= maxCharLength) { - batches.add(new ArrayList<>()); - currentBatchIndex++; - currentBatchLength = 0; - } - batches.get(currentBatchIndex) - .add(element); - currentBatchLength += elementLength; - } - return batches; - } - - private Flux getServiceInstancesByNames(List serviceInstanceNames) { - String spaceGuid = getTargetSpaceGuid().toString(); - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .spaceId(spaceGuid) - .addAllServiceInstanceNames( - serviceInstanceNames) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceInstancesV3() - .list(pageRequestSupplier.apply(page))); - } - - @Override - public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { - List allServiceInstances = new ArrayList<>(); - for (List batchOfServiceInstanceNames : toBatches(names, MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) { - List serviceInstances = fetchList(() -> getServiceInstancesByNames(batchOfServiceInstanceNames), - ImmutableRawV3CloudServiceInstance::of); - allServiceInstances.addAll(serviceInstances); - } - return allServiceInstances; - } - - @Override - public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .labelSelector(labelSelector) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - - return fetchListWithAuxiliaryContent(() -> getServiceInstanceResources(pageRequestSupplier), - this::zipWithAuxiliaryServiceInstanceContent); - } - - @Override - public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .labelSelector(labelSelector) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - - return fetchList(() -> getServiceInstanceResources(pageRequestSupplier), ImmutableRawCloudServiceInstance::of); - } - - @Override - public CloudStack getStack(String name) { - return getStack(name, true); - } - - @Override - public CloudStack getStack(String name, boolean required) { - CloudStack stack = findStackResource(name); - if (stack == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Stack " + name + " not found."); - } - return stack; - } - - @Override - public List getStacks() { - return fetchList(this::getStackResources, ImmutableRawCloudStack::of); - } - - @Override - public void rename(String applicationName, String newName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .update(UpdateApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .name(newName) - .build()) - .onErrorResume(t -> doesErrorMatchStatusCode(t, HttpStatus.SERVICE_UNAVAILABLE), - exception -> fallbackApplicationRename(newName, applicationGuid, exception)) - .block(); - } - - private Mono fallbackApplicationRename(String newName, UUID applicationGuid, Throwable exception) { - String existingApplicationName = getApplicationByGuid(applicationGuid).block() - .getName(); - if (newName.equals(existingApplicationName)) { - return Mono.empty(); - } - return Mono.error(exception); - } - - @Override - public void restartApplication(String applicationName) { - stopApplication(applicationName); - startApplication(applicationName); - } - - @Override - public void startApplication(String applicationName) { - Application application = getApplicationByName(applicationName).block(); - if (application.getState() == ApplicationState.STARTED) { - return; - } - UUID applicationGuid = UUID.fromString(application.getId()); - delegate.applicationsV3() - .start(StartApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .block(); - } - - @Override - public void stopApplication(String applicationName) { - Application application = getApplicationByName(applicationName).block(); - if (application.getState() == ApplicationState.STOPPED) { - return; - } - UUID applicationGuid = UUID.fromString(application.getId()); - delegate.applicationsV3() - .stop(StopApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .block(); - } - - @Override - public List unbindServiceInstance(String applicationName, String serviceInstanceName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); - - return doUnbindServiceInstance(applicationGuid, serviceInstanceGuid); - } - - @Override - public Optional deleteServiceBinding(UUID serviceBindingGuid) { - return doDeleteServiceBindings(List.of(serviceBindingGuid)).stream() - .findFirst(); - } - - @Override - public List unbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { - return doUnbindServiceInstance(applicationGuid, serviceInstanceGuid); - } - - @Override - public void updateApplicationDiskQuota(String applicationName, int diskQuota) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .scale(ScaleApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .type("web") - .diskInMb(diskQuota) - .build()) - .block(); - } - - @Override - public void updateApplicationEnv(String applicationName, Map env) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .updateEnvironmentVariables(UpdateApplicationEnvironmentVariablesRequest.builder() - .applicationId(applicationGuid.toString()) - .vars(env) - .build()) - .block(); - } - - @Override - public void updateApplicationInstances(String applicationName, int instances) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .scale(ScaleApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .type("web") - .instances(instances) - .build()) - .block(); - } - - @Override - public void updateApplicationMemory(String applicationName, int memory) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .scale(ScaleApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .type("web") - .memoryInMb(memory) - .build()) - .block(); - } - - @Override - public void updateApplicationMetadata(UUID guid, org.cloudfoundry.client.v3.Metadata metadata) { - delegate.applicationsV3() - .update(org.cloudfoundry.client.v3.applications.UpdateApplicationRequest.builder() - .applicationId(guid.toString()) - .metadata(metadata) - .build()) - .block(); - } - - @Override - public void updateApplicationStaging(String applicationName, Staging staging) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .update(UpdateApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .lifecycle(buildApplicationLifecycle(staging)) - .build()) - .block(); - updateApplicationProcess(applicationGuid, staging); - } - - @Override - public void updateApplicationRoutes(String applicationName, Set updatedRoutes) { - UUID applicationGuid = getApplicationGuid(applicationName); - List appRoutes = fetchList(() -> getRouteResourcesByAppGuid(applicationGuid), ImmutableRawCloudRoute::of); - Set outdatedRoutes = getOutdatedRoutes(applicationGuid, appRoutes, updatedRoutes); - Set newRoutes = getNewRoutes(applicationGuid, appRoutes, updatedRoutes); - removeRoutes(outdatedRoutes, applicationGuid); - addRoutes(newRoutes, applicationGuid); - } - - private Set getOutdatedRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { - return currentRoutes.stream() - .filter(currentRoute -> isRouteOutdated(applicationGuid, currentRoute, updatedRoutes)) - .collect(Collectors.toSet()); - } - - private boolean isRouteOutdated(UUID applicationGuid, CloudRoute currentRoute, Set updatedRoutes) { - Optional updatedRoute = findRoute(currentRoute.getUrl(), updatedRoutes); - if (updatedRoute.isEmpty()) { - return true; - } - return isProtocolChanged(applicationGuid, currentRoute, updatedRoute.get()); - } - - private Optional findRoute(String url, Collection routes) { - return routes.stream() - .filter(route -> Objects.equals(url, route.getUrl())) - .findFirst(); - } - - private boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, CloudRoute updatedRoute) { - if (updatedRoute.getRequestedProtocol() == null) { - return false; - } - return currentRoute.getDestinations() - .stream() - .filter(routeDestination -> Objects.equals(routeDestination.getApplicationGuid(), applicationGuid)) - .noneMatch( - routeDestination -> Objects.equals(routeDestination.getProtocol(), updatedRoute.getRequestedProtocol())); - } - - private Set getNewRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { - return updatedRoutes.stream() - .filter(updatedRoute -> isRouteUpdated(applicationGuid, updatedRoute, currentRoutes)) - .collect(Collectors.toSet()); - } - - private boolean isRouteUpdated(UUID applicationGuid, CloudRoute updatedRoute, List currentRoutes) { - Optional currentRoute = findRoute(updatedRoute.getUrl(), currentRoutes); - if (currentRoute.isEmpty()) { - return true; - } - return isProtocolChanged(applicationGuid, currentRoute.get(), updatedRoute); - } - - @Override - public String updateServiceBroker(CloudServiceBroker serviceBroker) { - Assert.notNull(serviceBroker, "Service broker must not be null."); - - CloudServiceBroker existingBroker = getServiceBroker(serviceBroker.getName()); - UUID brokerGuid = existingBroker.getMetadata() - .getGuid(); - - return delegate.serviceBrokersV3() - .update(UpdateServiceBrokerRequest.builder() - .serviceBrokerId(brokerGuid.toString()) - .name(serviceBroker.getName()) - .authentication(BasicAuthentication.builder() - .username(serviceBroker.getUsername()) - .password(serviceBroker.getPassword()) - .build()) - .url(serviceBroker.getUrl()) - .build()) - .flatMap(response -> Mono.justOrEmpty(response.jobId())) - .block(); - } - - @Override - public void updateServiceInstanceMetadata(UUID guid, org.cloudfoundry.client.v3.Metadata metadata) { - delegate.serviceInstancesV3() - .update(UpdateServiceInstanceRequest.builder() - .serviceInstanceId(guid.toString()) - .metadata(metadata) - .build()) - .block(); - } - - @Override - public void updateServiceBindingMetadata(UUID guid, org.cloudfoundry.client.v3.Metadata metadata) { - delegate.serviceBindingsV3() - .update(UpdateServiceBindingRequest.builder() - .serviceBindingId(guid.toString()) - .metadata(metadata) - .build()) - .block(); - } - - @Override - public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { - CloudServiceBroker broker = getServiceBroker(name); - List servicePlans = findServicePlansByBrokerGuid(getGuid(broker)); - for (CloudServicePlan servicePlan : servicePlans) { - updateServicePlanVisibility(servicePlan, visibility); - } - } - - @Override - public CloudTask getTask(UUID taskGuid) { - return fetch(() -> getTaskResource(taskGuid), ImmutableRawCloudTask::of); - } - - @Override - public List getTasks(String applicationName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - return fetchList(() -> getTaskResourcesByApplicationGuid(applicationGuid), ImmutableRawCloudTask::of); - } - - @Override - public CloudTask runTask(String applicationName, CloudTask task) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - return createTask(applicationGuid, task); - } - - @Override - public CloudTask cancelTask(UUID taskGuid) { - return fetch(() -> cancelTaskResource(taskGuid), ImmutableRawCloudTask::of); - } - - @Override - public CloudPackage asyncUploadApplication(String applicationName, Path file, UploadStatusCallback callback, Duration uploadTimeout) { - CloudPackage cloudPackage = startUpload(applicationName, file, uploadTimeout); - processAsyncUploadInBackground(cloudPackage, callback); - return cloudPackage; - } - - @Override - public Upload getUploadStatus(UUID packageGuid) { - CloudPackage cloudPackage = getPackage(packageGuid); - ErrorDetails errorDetails = null; - if (cloudPackage.getType() == CloudPackage.Type.BITS) { - errorDetails = ImmutableErrorDetails.builder() - .description(((BitsData) cloudPackage.getData()).getError()) - .build(); - } - return ImmutableUpload.builder() - .status(cloudPackage.getStatus()) - .errorDetails(errorDetails) - .build(); - } - - @Override - public CloudBuild getBuild(UUID buildGuid) { - return fetch(() -> getBuildResource(buildGuid), ImmutableRawCloudBuild::of); - } - - @Override - public List getBuildsForApplication(UUID applicationGuid) { - return fetchList(() -> getBuildResourcesByApplicationGuid(applicationGuid), ImmutableRawCloudBuild::of); - } - - @Override - public List getBuildsForPackage(UUID packageGuid) { - return fetchList(() -> getBuildResourcesByPackageGuid(packageGuid), ImmutableRawCloudBuild::of); - } - - @Override - public CloudBuild createBuild(UUID packageGuid) { - return fetch(() -> createBuildResource(packageGuid), ImmutableRawCloudBuild::of); - } - - @Override - public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { - delegate.applicationsV3() - .setCurrentDroplet(SetApplicationCurrentDropletRequest.builder() - .applicationId(applicationGuid.toString()) - .data(Relationship.builder() - .id(dropletGuid.toString()) - .build()) - .build()) - .block(); - } - - @Override - public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { - GetApplicationCurrentDropletResponse dropletResponse = delegate.applicationsV3() - .getCurrentDroplet(GetApplicationCurrentDropletRequest.builder() - .applicationId( - applicationGuid.toString()) - .build()) - .block(); - if (dropletResponse == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not found", - "Application with guid " + applicationGuid + " does not have a droplet"); - } - return parseDropletInfo(dropletResponse); - } - - private DropletInfo parseDropletInfo(GetApplicationCurrentDropletResponse getApplicationCurrentDropletResponse) { - String packageUrl = getApplicationCurrentDropletResponse.getLinks() - .get(Constants.PACKAGE) - .getHref(); - if (packageUrl.endsWith("/")) { - packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf("/")); - } - String packageGuid = packageUrl.substring(packageUrl.lastIndexOf("/") + 1); - return ImmutableDropletInfo.builder() - .guid(UUID.fromString(getApplicationCurrentDropletResponse.getId())) - .packageGuid(UUID.fromString(packageGuid)) - .build(); - } - - @Override - public List getPackagesForApplication(UUID applicationGuid) { - return fetchList(() -> getPackages(applicationGuid.toString()), ImmutableRawCloudPackage::of); - } - - private Flux getPackages(String applicationGuid) { - IntFunction pageRequestSupplier = page -> ListApplicationPackagesRequest.builder() - .page(page) - .applicationId( - applicationGuid) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .listPackages(pageRequestSupplier.apply(page))); - } - - @Override - public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { - Supplier> setFactory = () -> EnumSet.noneOf(UserRole.class); - return fetchFlux(() -> getRoles(spaceGuid, userGuid), ImmutableRawUserRole::of).collect(Collectors.toCollection(setFactory)) - .block(); - } - - public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { - org.cloudfoundry.client.v3.packages.DockerData.Builder dockerDataBuilder = org.cloudfoundry.client.v3.packages.DockerData.builder() - .image( - dockerInfo.getImage()); - if (dockerInfo.getCredentials() != null) { - addNonNullDockerCredentials(dockerInfo.getCredentials(), dockerDataBuilder); - } - CreatePackageRequest packageRequest = CreatePackageRequest.builder() - .type(PackageType.DOCKER) - .data(dockerDataBuilder.build()) - .relationships(buildPackageRelationships(applicationGuid)) - .build(); - CreatePackageResponse packageResponse = delegate.packages() - .create(packageRequest) - .block(); - return getPackage(UUID.fromString(packageResponse.getId())); - } - - @Override - public CloudAsyncJob getAsyncJob(String jobId) { - return fetch(() -> delegate.jobsV3() - .get(GetJobRequest.builder() - .jobId(jobId) - .build()), ImmutableRawCloudAsyncJob::of); - } - - private void addNonNullDockerCredentials(DockerCredentials dockerCredentials, - org.cloudfoundry.client.v3.packages.DockerData.Builder dockerDataBuilder) { - String username = dockerCredentials.getUsername(); - if (username != null) { - dockerDataBuilder.username(username); - } - String password = dockerCredentials.getPassword(); - if (password != null) { - dockerDataBuilder.password(password); - } - } - - private Flux getRoles(UUID spaceGuid, UUID userGuid) { - IntFunction pageRequestSupplier = page -> ListRolesRequest.builder() - .page(page) - .spaceId(spaceGuid.toString()) - .userId(userGuid.toString()) - .types(RoleType.values()) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.rolesV3() - .list(pageRequestSupplier.apply(page))); - } - - private CloudApplication findApplicationByName(String name, boolean required) { - CloudApplication application = findApplicationByName(name); - if (application == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + name + " not found."); - } - return application; - } - - private CloudApplication findApplicationByName(String name) { - return fetch(() -> getApplicationByName(name), application -> ImmutableRawCloudApplication.builder() - .application(application) - .space(target) - .build()); - } - - private Flux getApplicationResources() { - assertSpaceProvided("get application"); - IntFunction pageRequestSupplier = page -> ListApplicationsRequest.builder() - .spaceId(getTargetSpaceGuid().toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Mono getApplicationByGuid(UUID guid) { - return delegate.applicationsV3() - .get(GetApplicationRequest.builder() - .applicationId(guid.toString()) - .build()); - } - - private Mono getApplicationByName(String name) { - assertSpaceProvided("get application"); - IntFunction pageRequestSupplier = page -> ListApplicationsRequest.builder() - .spaceId(getTargetSpaceGuid().toString()) - .name(name) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - private CloudServiceInstance findServiceInstanceByName(String name) { - return fetchWithAuxiliaryContent(() -> getServiceInstanceResourceByName(name), this::zipWithAuxiliaryServiceInstanceContent); - } - - private Mono getServiceInstanceByGuid(UUID serviceInstanceGuid) { - return delegate.serviceInstancesV3() - .get(GetServiceInstanceRequest.builder() - .serviceInstanceId(serviceInstanceGuid.toString()) - .build()); - } - - private Mono getServiceInstanceResourceByName(String name) { - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .spaceId( - getTargetSpaceGuid().toString()) - .serviceInstanceName(name) - .page(page) - .build(); - return getServiceInstanceResources(pageRequestSupplier).singleOrEmpty(); - } - - private Flux getServiceInstanceResources(IntFunction pageRequestSupplier) { - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceInstancesV3() - .list(pageRequestSupplier.apply(page))); - } - - private Mono> zipWithAuxiliaryServiceInstanceContent(ServiceInstanceResource serviceInstanceResource) { - if (isUserProvided(serviceInstanceResource)) { - return Mono.just(ImmutableRawCloudServiceInstance.of(serviceInstanceResource)); - } - String servicePlanGuid = serviceInstanceResource.getRelationships() - .getServicePlan() - .getData() - .getId(); - - return getServicePlanResource(servicePlanGuid, serviceInstanceResource.getName()).zipWhen(servicePlan -> getServiceOffering( - servicePlan.getRelationships() - .getServiceOffering() - .getData() - .getId())) - .map( - tuple -> ImmutableRawCloudServiceInstance.builder() - .resource( - serviceInstanceResource) - .servicePlan( - tuple.getT1()) - .serviceOffering( - tuple.getT2()) - .build()); - } - - private boolean isUserProvided(ServiceInstanceResource serviceInstanceResource) { - return ServiceInstanceType.USER_PROVIDED.equals(serviceInstanceResource.getType()); - } - - private Flux getServiceBindingResourcesByServiceInstanceGuid(UUID serviceInstanceGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .serviceInstanceId( - serviceInstanceGuid.toString()) - .type( - ServiceBindingType.APPLICATION) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getServiceBindingResourcesByApplicationGuidAndServiceInstanceGuid(UUID applicationGuid, - UUID serviceInstanceGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .applicationId( - applicationGuid.toString()) - .serviceInstanceId( - serviceInstanceGuid.toString()) - .page(page) - .build(); - return getApplicationServiceBindingResources(pageRequestSupplier); - } - - private Flux getServiceBindingResourcesByApplicationGuid(UUID applicationGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .applicationId( - applicationGuid.toString()) - .page(page) - .build(); - return getApplicationServiceBindingResources(pageRequestSupplier); - } - - private Flux getApplicationServiceBindingResources( - IntFunction pageRequestSupplier) { - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))); - } - - private List findServicePlansByBrokerGuid(UUID brokerGuid) { - List offerings = findServiceOfferingsByBrokerGuid(brokerGuid); - return getPlans(offerings); - } - - private List getPlans(List offerings) { - return offerings.stream() - .map(CloudServiceOffering::getServicePlans) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } - - private void updateServicePlanVisibility(CloudServicePlan servicePlan, ServicePlanVisibility visibility) { - updateServicePlanVisibility(getGuid(servicePlan), visibility); - } - - private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { - delegate.servicePlansV3() - .updateVisibility(UpdateServicePlanVisibilityRequest.builder() - .servicePlanId(servicePlanGuid.toString()) - .type(Visibility.from(visibility.toString())) - .build()) - .block(); - } - - private Mono getTaskResource(UUID guid) { - GetTaskRequest request = GetTaskRequest.builder() - .taskId(guid.toString()) - .build(); - return delegate.tasks() - .get(request); - } - - private Flux getTaskResourcesByApplicationGuid(UUID applicationGuid) { - IntFunction pageRequestSupplier = page -> ListTasksRequest.builder() - .applicationId(applicationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.tasks() - .list(pageRequestSupplier.apply(page))); - } - - private CloudTask createTask(UUID applicationGuid, CloudTask task) { - return fetch(() -> createTaskResource(applicationGuid, task), ImmutableRawCloudTask::of); - } - - private Mono createTaskResource(UUID applicationGuid, CloudTask task) { - CreateTaskRequest request = CreateTaskRequest.builder() - .applicationId(applicationGuid.toString()) - .command(task.getCommand()) - .name(task.getName()) - .memoryInMb(task.getLimits() - .getMemory()) - .diskInMb(task.getLimits() - .getDisk()) - .build(); - return delegate.tasks() - .create(request); - } - - private Mono cancelTaskResource(UUID taskGuid) { - CancelTaskRequest request = CancelTaskRequest.builder() - .taskId(taskGuid.toString()) - .build(); - return delegate.tasks() - .cancel(request); - } - - private CloudPackage startUpload(String applicationName, Path file, Duration uploadTimeout) { - Assert.notNull(applicationName, "AppName must not be null"); - Assert.notNull(file, "File must not be null"); - - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - UUID packageGuid = getGuid(createPackageForApplication(applicationGuid)); - - delegate.packages() - .upload(UploadPackageRequest.builder() - .bits(file) - .packageId(packageGuid.toString()) - .build()) - .timeout(uploadTimeout) - .block(); - - return getPackage(packageGuid); - } - - private CloudPackage createPackageForApplication(UUID applicationGuid) { - return fetch(() -> createPackageResource(applicationGuid), ImmutableRawCloudPackage::of); - } - - private Mono createPackageResource(UUID applicationGuid) { - CreatePackageRequest request = CreatePackageRequest.builder() - .type(PackageType.BITS) - .relationships(buildPackageRelationships(applicationGuid)) - .build(); - return delegate.packages() - .create(request); - } - - private PackageRelationships buildPackageRelationships(UUID applicationGuid) { - return PackageRelationships.builder() - .application(buildToOneRelationship(applicationGuid)) - .build(); - } - - private ToOneRelationship buildToOneRelationship(UUID guid) { - return ToOneRelationship.builder() - .data(buildRelationship(guid.toString())) - .build(); - } - - private ToOneRelationship buildToOneRelationship(String guid) { - return ToOneRelationship.builder() - .data(buildRelationship(guid)) - .build(); - } - - private Relationship buildRelationship(String guid) { - return Relationship.builder() - .id(guid) - .build(); - } - - private Mono createBuildResource(UUID packageGuid) { - CreateBuildRequest request = CreateBuildRequest.builder() - .getPackage(buildRelationship(packageGuid.toString())) - .build(); - return delegate.builds() - .create(request); - } - - private Mono getBuildResource(UUID buildGuid) { - GetBuildRequest request = GetBuildRequest.builder() - .buildId(buildGuid.toString()) - .build(); - return delegate.builds() - .get(request); - } - - private Flux getBuildResourcesByApplicationGuid(UUID applicationGuid) { - IntFunction pageRequestSupplier = page -> ListApplicationBuildsRequest.builder() - .applicationId( - applicationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .listBuilds(pageRequestSupplier.apply(page))); - } - - private Flux getBuildResourcesByPackageGuid(UUID packageGuid) { - IntFunction pageRequestSupplier = page -> ListBuildsRequest.builder() - .packageId(packageGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.builds() - .list(pageRequestSupplier.apply(page))); - } - - private void assertSpaceProvided(String operation) { - Assert.notNull(target, "Unable to " + operation + " without specifying organization and space to use."); - } - - private void removeRoutes(Set routes, UUID applicationGuid) { - for (CloudRoute route : routes) { - for (RouteDestination destination : route.getDestinations()) { - if (destination.getApplicationGuid() - .equals(applicationGuid)) { - unbindRoute(route.getGuid(), destination.getGuid()); - } - } - } - } - - private void validateDomainForRoute(CloudRoute route, Map existingDomains) { - String domain = route.getDomain() - .getName(); - if (!StringUtils.hasLength(domain) || !existingDomains.containsKey(domain)) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), - "Domain '" + domain + "' not found for URI " + route.getUrl()); - } - } - - private void unbindRoute(UUID routeGuid, UUID destinationGuid) { - delegate.routesV3() - .removeDestinations(RemoveRouteDestinationsRequest.builder() - .routeId(routeGuid.toString()) - .destinationId(destinationGuid.toString()) - .build()) - .block(); - } - - private void bindRoute(UUID routeGuid, UUID applicationGuid, String protocol) { - delegate.routesV3() - .insertDestinations(InsertRouteDestinationsRequest.builder() - .routeId(routeGuid.toString()) - .destination(createDestination(applicationGuid, protocol)) - .build()) - .block(); - } - - private Destination createDestination(UUID applicationGuid, String protocol) { - return Destination.builder() - .application(org.cloudfoundry.client.v3.routes.Application.builder() - .applicationId(applicationGuid.toString()) - .build()) - .protocol(protocol) - .build(); - } - - private UUID getOrAddRoute(UUID domainGuid, String host, String path) { - UUID routeGuid = getRouteGuid(domainGuid, host, path); - if (routeGuid == null) { - routeGuid = doAddRoute(domainGuid, host, path); - } - return routeGuid; - } - - private UUID doAddRoute(UUID domainGuid, String host, String path) { - assertSpaceProvided("add route"); - CreateRouteResponse response = delegate.routesV3() - .create(CreateRouteRequest.builder() - .host(host) - .path(path) - .relationships(RouteRelationships.builder() - .domain(buildToOneRelationship( - domainGuid)) - .space(buildToOneRelationship( - getTargetSpaceGuid())) - .build()) - .build()) - .block(); - return getGuid(response); - } - - private void doCreateDomain(String name) { - delegate.domainsV3() - .create(CreateDomainRequest.builder() - .name(name) - .relationships(DomainRelationships.builder() - .organization( - buildToOneRelationship(getTargetOrganizationGuid())) - .build()) - .build()) - .block(); - } - - private void doDeleteDomain(UUID guid) { - delegate.domainsV3() - .delete(DeleteDomainRequest.builder() - .domainId(guid.toString()) - .build()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, DELETE_JOB_TIMEOUT, jobId)) - .block(); - } - - private void doDeleteRoute(UUID guid) { - delegate.routesV3() - .delete(DeleteRouteRequest.builder() - .routeId(guid.toString()) - .build()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, DELETE_JOB_TIMEOUT, jobId)) - .block(); - } - - private void doDeleteServiceInstance(UUID serviceInstanceGuid) { - delegate.serviceInstancesV3() - .delete(DeleteServiceInstanceRequest.builder() - .serviceInstanceId(serviceInstanceGuid.toString()) - .build()) - .block(); - } - - private List doUnbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { - List serviceBindingGuids = getServiceBindingGuids(applicationGuid, serviceInstanceGuid); - if (serviceBindingGuids.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Service binding between service with GUID " + serviceInstanceGuid - + " and application with GUID " + applicationGuid + " not found."); - } - return doDeleteServiceBindings(serviceBindingGuids); - } - - private List doDeleteServiceBindings(List guids) { - List jobIds = new ArrayList<>(); - List errors = new ArrayList<>(); - for (UUID guid : guids) { - try { - Optional.ofNullable(deleteSingleServiceBinding(guid)) - .ifPresent(jobIds::add); - } catch (AbstractCloudFoundryException e) { - errors.add(e); - } - } - throwOnErrors(errors); - return jobIds; - } - - private String deleteSingleServiceBinding(UUID guid) { - return delegate.serviceBindingsV3() - .delete(DeleteServiceBindingRequest.builder() - .serviceBindingId(guid.toString()) - .build()) - .block(); - } - - private void throwOnErrors(List errors) { - if (errors.isEmpty()) { - return; - } - RuntimeException first = errors.get(0); - errors.subList(1, errors.size()) - .forEach(first::addSuppressed); - throw first; - } - - private CloudDomain findDomainByName(String name, boolean required) { - CloudDomain domain = findDomainByName(name); - if (domain == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Domain " + name + " not found."); - } - return domain; - } - - private CloudDomain findDomainByName(String name) { - return fetch(() -> getDomainResourceByName(name), ImmutableRawCloudDomain::of); - } - - private List findDomainsByOrganizationGuid(UUID organizationGuid) { - return fetchList(() -> getDomainResourcesByOrganizationGuid(organizationGuid), ImmutableRawCloudDomain::of); - } - - private Mono getDomainResourceByName(String name) { - IntFunction pageRequestSupplier = page -> ListDomainsRequest.builder() - .name(name) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.domainsV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - private Flux getDomainResources() { - IntFunction pageRequestSupplier = page -> ListDomainsRequest.builder() - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.domainsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getSharedDomainResources() { - return getDomainResources().filter(domain -> domain.getRelationships() - .getOrganization() - .getData() == null); - } - - private Flux getPrivateDomainResources() { - return getDomainResources().filter(domain -> domain.getRelationships() - .getOrganization() - .getData() != null); - } - - private Flux getDomainResourcesByOrganizationGuid(UUID organizationGuid) { - IntFunction pageRequestSupplier = page -> ListOrganizationDomainsRequest.builder() - .organizationId( - organizationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.organizationsV3() - .listDomains(pageRequestSupplier.apply(page))); - } - - private Flux getDomainResourcesByNamesInBatches(Set names) { - return Flux.fromIterable(toBatches(names, MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) - .flatMap(this::getDomainResourcesByNames); - } - - private Flux getDomainResourcesByNames(Collection names) { - if (names.isEmpty()) { - return Flux.empty(); - } - IntFunction pageRequestSupplier = page -> ListDomainsRequest.builder() - .names(names) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.domainsV3() - .list(pageRequestSupplier.apply(page))); - } - - private List findRoutes(CloudDomain domain) { - return fetchList(() -> getRouteResourcesByDomainGuidAndSpaceGuid(domain.getGuid(), getTargetSpaceGuid()), - ImmutableRawCloudRoute::of); - } - - private Flux getRouteResourcesByDomainGuidAndSpaceGuid(UUID domainGuid, UUID spaceGuid) { - IntFunction pageRequestSupplier = page -> ListRoutesRequest.builder() - .domainId(domainGuid.toString()) - .spaceId(spaceGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.routesV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getRouteResourcesByDomainGuidHostAndPath(UUID domainGuid, String host, String path) { - ListRoutesRequest.Builder requestBuilder = ListRoutesRequest.builder(); - if (host != null) { - requestBuilder.host(host); - } - if (path != null) { - requestBuilder.path(path); - } - requestBuilder.spaceId(getTargetSpaceGuid().toString()) - .domainId(domainGuid.toString()); - - return PaginationUtils.requestClientV3Resources(page -> delegate.routesV3() - .list(requestBuilder.page(page) - .build())); - } - - private Flux getRouteResourcesByAppGuid(UUID applicationGuid) { - IntFunction pageSupplier = page -> ListApplicationRoutesRequest.builder() - .applicationId( - applicationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .listRoutes(pageSupplier.apply(page))); - } - - private List findServiceOfferingsByBrokerGuid(UUID brokerGuid) { - return fetchListWithAuxiliaryContent(() -> getServiceResourcesByBrokerGuid(brokerGuid), - this::zipWithAuxiliaryServiceOfferingContent); - } - - private List findServiceOfferingsByLabel(String label) { - Assert.notNull(label, "Service label must not be null"); - return fetchListWithAuxiliaryContent(() -> getServiceResourcesByLabel(label), this::zipWithAuxiliaryServiceOfferingContent); - } - - private List findServiceOfferingsByLabelAndBrokerName(String label, String brokerName) { - Assert.notNull(label, "Service label must not be null"); - Assert.notNull(brokerName, "Service broker must not be null"); - return fetchListWithAuxiliaryContent(() -> getServiceResourcesByLabelAndBrokerName(label, brokerName), - this::zipWithAuxiliaryServiceOfferingContent); - } - - private Flux getServiceResources() { - IntFunction pageRequestSupplier = page -> ListServiceOfferingsRequest.builder() - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - return getServiceResources(pageRequestSupplier); - } - - protected Mono getServiceOffering(String offeringId) { - GetServiceOfferingRequest request = GetServiceOfferingRequest.builder() - .serviceOfferingId(offeringId) - .build(); - return delegate.serviceOfferingsV3() - .get(request) - .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.FORBIDDEN), - t -> new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), - MessageFormat.format( - Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, offeringId), - t)) - .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), - t -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), - MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, - offeringId), t)); - } - - private Flux getServiceResourcesByBrokerGuid(UUID brokerGuid) { - IntFunction pageRequestSupplier = page -> ListServiceOfferingsRequest.builder() - .serviceBrokerId( - brokerGuid.toString()) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - return getServiceResources(pageRequestSupplier); - } - - private Flux getServiceResourcesByLabel(String label) { - IntFunction pageRequestSupplier = page -> ListServiceOfferingsRequest.builder() - .name(label) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - return getServiceResources(pageRequestSupplier); - } - - private Flux getServiceResourcesByLabelAndBrokerName(String label, String brokerName) { - IntFunction pageRequestSupplier = page -> ListServiceOfferingsRequest.builder() - .name(label) - .serviceBrokerName(brokerName) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - return getServiceResources(pageRequestSupplier); - } - - private Flux getServiceResources(IntFunction pageRequestSupplier) { - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceOfferingsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Mono> zipWithAuxiliaryServiceOfferingContent(ServiceOfferingResource serviceOffering) { - UUID serviceOfferingGuid = getGuid(serviceOffering); - return getServicePlansFlux(serviceOfferingGuid).collectList() - .map(servicePlans -> ImmutableRawCloudServiceOffering.builder() - .serviceOffering( - serviceOffering) - .servicePlans(servicePlans) - .build()); - } - - private Flux getServicePlansFlux(UUID serviceOfferingGuid) { - return fetchFlux(() -> getServicePlanResourcesByServiceOfferingGuid(serviceOfferingGuid), ImmutableRawCloudServicePlan::of); - } - - protected Mono getServicePlanResource(String servicePlanGuid, String serviceInstanceName) { - GetServicePlanRequest request = GetServicePlanRequest.builder() - .servicePlanId(servicePlanGuid) - .build(); - - return delegate.servicePlansV3() - .get(request) - .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.FORBIDDEN), - t -> new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), - MessageFormat.format( - Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, - servicePlanGuid, serviceInstanceName), t)) - .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), - t -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), - MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, - serviceInstanceName), t)); - } - - private Flux getServicePlanResourcesByServiceOfferingGuid(UUID serviceOfferingGuid) { - IntFunction pageRequestSupplier = page -> ListServicePlansRequest.builder() - .serviceOfferingId( - serviceOfferingGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.servicePlansV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getServiceKeyResource(CloudServiceInstance serviceInstance) { - UUID serviceInstanceGuid = getGuid(serviceInstance); - return getServiceKeyResourcesByServiceInstanceGuid(serviceInstanceGuid); - } - - private Flux getServiceKeyResourcesByServiceInstanceGuid(UUID serviceInstanceGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .serviceInstanceId( - serviceInstanceGuid.toString()) - .type(ServiceBindingType.KEY) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Mono getServiceKeyResourceByNameAndServiceInstanceGuid(String name, UUID guid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .serviceInstanceId(guid.toString()) - .type(ServiceBindingType.KEY) - .name(name) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - private CloudStack findStackResource(String name) { - return fetch(() -> getStackResourceByName(name), ImmutableRawCloudStack::of); - } - - private Flux getStackResources() { - IntFunction pageRequestSupplier = page -> ListStacksRequest.builder() - .page(page) - .build(); - return getStackResources(pageRequestSupplier); - } - - private Mono getStackResourceByName(String name) { - IntFunction pageRequestSupplier = page -> ListStacksRequest.builder() - .name(name) - .page(page) - .build(); - return getStackResources(pageRequestSupplier).singleOrEmpty(); - } - - private Flux getStackResources(IntFunction requestForPage) { - return PaginationUtils.requestClientV3Resources(page -> delegate.stacksV3() - .list(requestForPage.apply(page))); - } - - private List findEventsByTarget(String target) { - return fetchList(() -> getEventResourcesByTarget(target), ImmutableRawCloudEvent::of); - } - - private Flux getEventResources() { - IntFunction pageRequestSupplier = page -> ListAuditEventsRequest.builder() - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.auditEventsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getEventResourcesByTarget(String target) { - IntFunction pageRequestSupplier = page -> ListAuditEventsRequest.builder() - .targetId(target) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.auditEventsV3() - .list(pageRequestSupplier.apply(page))); - } - - private InstancesInfo findApplicationInstances(UUID applicationGuid) { - return fetch(() -> getApplicationProcessStatsResource(applicationGuid), ImmutableRawInstancesInfo::of); - } - - private Mono getApplicationProcessStatsResource(UUID applicationGuid) { - return delegate.applicationsV3() - .getProcessStatistics(GetApplicationProcessStatisticsRequest.builder() - .applicationId(applicationGuid.toString()) - .type("web") - .build()); - } - - private CloudServiceBroker findServiceBrokerByName(String name) { - return fetch(() -> getServiceBrokerResourceByName(name), ImmutableRawCloudServiceBroker::of); - } - - private Flux getServiceBrokerResources() { - IntFunction pageRequestSupplier = page -> ListServiceBrokersRequest.builder() - .page(page) - .build(); - return getServiceBrokerResources(pageRequestSupplier); - } - - private Mono getServiceBrokerResourceByName(String name) { - IntFunction pageRequestSupplier = page -> ListServiceBrokersRequest.builder() - .page(page) - .name(name) - .build(); - return getServiceBrokerResources(pageRequestSupplier).singleOrEmpty(); - } - - private Flux getServiceBrokerResources(IntFunction pageRequestSupplier) { - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBrokersV3() - .list(pageRequestSupplier.apply(page))); - } - - private CloudServicePlan findPlanForService(CloudServiceInstance service, String planName) { - List offerings = getServiceOfferings(service); - for (var offering : offerings) { - for (var plan : offering.getServicePlans()) { - if (planName.equals(plan.getName())) { - return plan; - } - } - } - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service plan " + planName + " not found."); - } - - private List getServiceOfferings(CloudServiceInstance service) { - if (service.getBroker() == null) { - return findServiceOfferingsByLabel(service.getLabel()); - } - return findServiceOfferingsByLabelAndBrokerName(service.getLabel(), service.getBroker()); - } - - private UUID getRequiredApplicationGuid(String name) { - Resource applicationResource = getApplicationByName(name).block(); - if (applicationResource == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + name + " not found."); - } - return UUID.fromString(applicationResource.getId()); - } - - private Mono getServiceInstanceByName(String name) { - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .spaceId( - getTargetSpaceGuid().toString()) - .serviceInstanceName(name) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceInstancesV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - private UUID getRequiredDomainGuid(String name) { - return getGuid(findDomainByName(name, true)); - } - - private Map getDomainsFromRoutes(Set routes) { - Set domainNames = routes.stream() - .map(route -> route.getDomain() - .getName()) - .filter(StringUtils::hasLength) - .collect(Collectors.toSet()); - return getDomainResourcesByNamesInBatches(domainNames).collectMap(DomainResource::getName, - domain -> UUID.fromString(domain.getId())) - .block(); - } - - private UUID getRouteGuid(UUID domainGuid, String host, String path) { - List routeEntitiesResource = getRouteResourcesByDomainGuidHostAndPath(domainGuid, host, path).collect( - Collectors.toList()) - .block(); - if (CollectionUtils.isEmpty(routeEntitiesResource)) { - return null; - } - return getGuid(routeEntitiesResource.get(0)); - } - - private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInstanceGuid) { - return getServiceBindingResourcesByApplicationGuidAndServiceInstanceGuid(applicationGuid, serviceInstanceGuid).map(this::getGuid) - .collectList() - .block(); - } - - private void processAsyncUploadInBackground(CloudPackage cloudPackage, UploadStatusCallback callback) { - String threadName = String.format("App upload monitor: %s", cloudPackage.getGuid()); - new Thread(() -> processAsyncUpload(cloudPackage, callback), threadName).start(); - } - - private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback callback) { - while (true) { - Upload upload = getUploadStatus(cloudPackage.getGuid()); - Status uploadStatus = upload.getStatus(); - boolean unsubscribe = callback.onProgress(uploadStatus.toString()); - if (unsubscribe || isUploadReady(uploadStatus)) { - return; - } - if (hasUploadFailed(uploadStatus)) { - callback.onError(upload.getErrorDetails() - .getDescription()); - return; - } - try { - Thread.sleep(PACKAGE_UPLOAD_JOB_POLLING_PERIOD); - } catch (InterruptedException e) { - return; - } - } - } - - private boolean isUploadReady(Status status) { - return status == Status.READY; - } - - private boolean hasUploadFailed(Status status) { - return status == Status.EXPIRED || status == Status.FAILED; - } - - @Override - public CloudPackage getPackage(UUID packageGuid) { - return fetch(() -> getPackageResource(packageGuid), ImmutableRawCloudPackage::of); - } - - private Mono getPackageResource(UUID guid) { - GetPackageRequest request = GetPackageRequest.builder() - .packageId(guid.toString()) - .build(); - return delegate.packages() - .get(request); - } - - private UUID getTargetOrganizationGuid() { - return getGuid(target.getOrganization()); - } - - private UUID getTargetSpaceGuid() { - return getGuid(target); - } - - private UUID getGuid(CloudEntity entity) { - return Optional.ofNullable(entity) - .map(CloudEntity::getMetadata) - .map(CloudMetadata::getGuid) - .orElse(null); - } - - private UUID getGuid(Resource resource) { - return Optional.ofNullable(resource) - .map(Resource::getId) - .map(UUID::fromString) - .orElse(null); - } - - private boolean doesErrorMatchStatusCode(Throwable t, HttpStatus status) { - if (t instanceof AbstractCloudFoundryException e) { - return e.getStatusCode() == status.value(); - } - return false; - } - - private > Flux fetchFluxWithAuxiliaryContent(Supplier> resourceSupplier, - Function> resourceMapper) { - return resourceSupplier.get() - .flatMap(resourceMapper) - .map(Derivable::derive); - } - - private > List fetchListWithAuxiliaryContent(Supplier> resourceSupplier, - Function> resourceMapper) { - return fetchFluxWithAuxiliaryContent(resourceSupplier, resourceMapper).collectList() - .block(); - } - - private > List fetchList(Supplier> resourceSupplier, Function resourceMapper) { - return fetchFlux(resourceSupplier, resourceMapper).collectList() - .block(); - } - - private > Flux fetchFlux(Supplier> resourceSupplier, Function resourceMapper) { - return resourceSupplier.get() - .map(resourceMapper) - .map(Derivable::derive); - } - - private > T fetchWithAuxiliaryContent(Supplier> resourceSupplier, - Function> resourceMapper) { - return fetchMonoWithAuxiliaryContent(resourceSupplier, resourceMapper).block(); - } - - private > T fetch(Supplier> resourceSupplier, Function resourceMapper) { - return fetchMono(resourceSupplier, resourceMapper).block(); - } - - private > Mono fetchMonoWithAuxiliaryContent(Supplier> resourceSupplier, - Function> resourceMapper) { - return resourceSupplier.get() - .flatMap(resourceMapper) - .map(Derivable::derive); - } - - private > Mono fetchMono(Supplier> resourceSupplier, Function resourceMapper) { - return resourceSupplier.get() - .map(resourceMapper) - .map(Derivable::derive); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java new file mode 100644 index 0000000000..116a936d3f --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java @@ -0,0 +1,649 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.nio.file.Path; +import java.text.MessageFormat; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.springframework.http.HttpStatus; +import org.springframework.web.client.RestClient; + +public class CloudControllerRestClientV3Impl implements CloudControllerRestClient { + + private final CloudSpace targetSpace; + + private final ApplicationsV3Operations applicationsOperations; + private final BuildsV3Operations buildsOperations; + private final DomainsV3Operations domainsOperations; + private final EventsV3Operations eventsOperations; + private final JobsV3Operations jobsOperations; + private final PackagesV3Operations packagesOperations; + private final ProcessesV3Operations processesOperations; + private final RolesV3Operations rolesOperations; + private final RoutesV3Operations routesOperations; + private final ServiceBindingsV3Operations serviceBindingsOperations; + private final ServiceBrokersV3Operations serviceBrokersOperations; + private final ServiceInstancesV3Operations serviceInstancesOperations; + private final ServiceKeysV3Operations serviceKeysOperations; + private final ServiceOfferingsV3Operations serviceOfferingsOperations; + private final StacksV3Operations stacksOperations; + private final TasksV3Operations tasksOperations; + + public CloudControllerRestClientV3Impl(CloudSpace targetSpace, CloudControllerV3Client cloudControllerClient, + Function uploadRestClientFactory) { + this.targetSpace = targetSpace; + this.applicationsOperations = new ApplicationsV3Operations(cloudControllerClient, targetSpace); + this.buildsOperations = new BuildsV3Operations(cloudControllerClient); + this.domainsOperations = new DomainsV3Operations(cloudControllerClient, targetSpace); + this.eventsOperations = new EventsV3Operations(cloudControllerClient, targetSpace); + this.jobsOperations = new JobsV3Operations(cloudControllerClient); + this.packagesOperations = new PackagesV3Operations(cloudControllerClient, targetSpace, uploadRestClientFactory); + this.processesOperations = new ProcessesV3Operations(cloudControllerClient, targetSpace); + this.rolesOperations = new RolesV3Operations(cloudControllerClient); + this.routesOperations = new RoutesV3Operations(cloudControllerClient, targetSpace); + this.serviceBindingsOperations = new ServiceBindingsV3Operations(cloudControllerClient, targetSpace); + this.serviceBrokersOperations = new ServiceBrokersV3Operations(cloudControllerClient, targetSpace); + this.serviceInstancesOperations = new ServiceInstancesV3Operations(cloudControllerClient, targetSpace); + this.serviceKeysOperations = new ServiceKeysV3Operations(cloudControllerClient); + this.serviceOfferingsOperations = new ServiceOfferingsV3Operations(cloudControllerClient, targetSpace); + this.stacksOperations = new StacksV3Operations(cloudControllerClient); + this.tasksOperations = new TasksV3Operations(cloudControllerClient, targetSpace); + } + + @Override + public CloudSpace getTarget() { + return targetSpace; + } + + @Override + public void addDomain(String domainName) { + domainsOperations.addDomain(domainName); + } + + @Override + public void addRoute(String host, String domainName, String path) { + routesOperations.addRoute(host, domainName, path); + } + + @Override + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName) { + return serviceBindingsOperations.bindServiceInstance(bindingName, applicationName, serviceInstanceName); + } + + @Override + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, + Map parameters) { + return serviceBindingsOperations.bindServiceInstance(bindingName, applicationName, serviceInstanceName, parameters); + } + + @Override + public void createApplication(ApplicationToCreateDto dto) { + applicationsOperations.createApplication(dto); + + if (dto.getStaging() != null) { + updateApplicationStaging(dto.getName(), dto.getStaging()); + } + + if (dto.getRoutes() != null && !dto.getRoutes() + .isEmpty()) { + updateApplicationRoutes(dto.getName(), dto.getRoutes()); + } + } + + @Override + public void createServiceInstance(CloudServiceInstance serviceInstance) { + serviceInstancesOperations.createServiceInstance(serviceInstance); + } + + @Override + public String createServiceBroker(CloudServiceBroker serviceBroker) { + return serviceBrokersOperations.createServiceBroker(serviceBroker); + } + + @Override + public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { + return serviceKeysOperations.createAndFetchServiceKey(keyModel, getServiceInstance(serviceInstanceName)); + } + + @Override + public Optional createServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { + return serviceKeysOperations.createServiceKey(keyModel, getServiceInstance(serviceInstanceName)); + } + + @Override + public Optional createServiceKey(String serviceInstanceName, String serviceKeyName, Map parameters) { + return serviceKeysOperations.createServiceKey(getServiceInstance(serviceInstanceName), serviceKeyName, parameters); + } + + @Override + public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { + serviceInstancesOperations.createUserProvidedServiceInstance(serviceInstance); + } + + @Override + public void deleteApplication(String applicationName) { + applicationsOperations.deleteApplication(applicationName); + } + + @Override + public void deleteDomain(String domainName) { + domainsOperations.deleteDomain(domainName); + } + + @Override + public void deleteOrphanedRoutes() { + routesOperations.deleteOrphanedRoutes(); + } + + @Override + public void deleteRoute(String host, String domainName, String path) { + routesOperations.deleteRoute(host, domainName, path); + } + + @Override + public void deleteServiceInstance(String serviceInstanceName) { + serviceInstancesOperations.deleteServiceInstance(serviceInstanceName); + } + + @Override + public void deleteServiceInstance(CloudServiceInstance serviceInstance) { + serviceInstancesOperations.deleteServiceInstance(serviceInstance); + } + + @Override + public String deleteServiceBroker(String name) { + return serviceBrokersOperations.deleteServiceBroker(name); + } + + @Override + public Optional deleteServiceBinding(String serviceInstanceName, String serviceKeyName) { + CloudServiceKey serviceKey = getServiceKey(serviceInstanceName, serviceKeyName); + + if (serviceKey == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_KEY_0_NOT_FOUND, serviceKeyName)); + } + + return deleteServiceBinding(serviceKey.getGuid()); + } + + @Override + public Optional deleteServiceBinding(UUID bindingGuid) { + return serviceBindingsOperations.deleteServiceBinding(bindingGuid); + } + + @Override + public CloudApplication getApplication(String applicationName) { + return applicationsOperations.getApplication(applicationName); + } + + @Override + public CloudApplication getApplication(String applicationName, boolean required) { + return applicationsOperations.getApplication(applicationName, required); + } + + @Override + public UUID getApplicationGuid(String applicationName) { + return applicationsOperations.getApplicationGuid(applicationName); + } + + @Override + public String getApplicationName(UUID applicationGuid) { + return applicationsOperations.getApplicationName(applicationGuid); + } + + @Override + public Map getApplicationEnvironment(UUID applicationGuid) { + return applicationsOperations.getApplicationEnvironment(applicationGuid); + } + + @Override + public Map getApplicationEnvironment(String applicationName) { + return applicationsOperations.getApplicationEnvironment(applicationName); + } + + @Override + public List getApplications() { + return applicationsOperations.getApplications(); + } + + @Override + public List getApplicationsByMetadataLabelSelector(String labelSelector) { + return applicationsOperations.getApplicationsByMetadataLabelSelector(labelSelector); + } + + @Override + public List getApplicationEvents(String applicationName) { + return eventsOperations.getApplicationEvents(applicationName); + } + + @Override + public List getEventsByTarget(UUID uuid) { + return eventsOperations.getEventsByTarget(uuid); + } + + @Override + public InstancesInfo getApplicationInstances(CloudApplication app) { + return processesOperations.getApplicationInstances(app); + } + + @Override + public InstancesInfo getApplicationInstances(UUID applicationGuid) { + return processesOperations.getApplicationInstances(applicationGuid); + } + + @Override + public CloudProcess getApplicationProcess(UUID applicationGuid) { + return processesOperations.getApplicationProcess(applicationGuid); + } + + @Override + public List getApplicationRoutes(UUID applicationGuid) { + return routesOperations.getApplicationRoutes(applicationGuid); + } + + @Override + public Map getApplicationFeatures(UUID applicationGuid) { + return processesOperations.getApplicationFeatures(applicationGuid); + } + + @Override + public CloudDomain getDefaultDomain() { + return domainsOperations.getDefaultDomain(); + } + + @Override + public List getDomains() { + return domainsOperations.getDomains(); + } + + @Override + public List getDomainsForOrganization() { + return domainsOperations.getDomainsForOrganization(); + } + + @Override + public List getEvents() { + return eventsOperations.getEvents(); + } + + @Override + public List getPrivateDomains() { + return domainsOperations.getPrivateDomains(); + } + + @Override + public List getRoutes(String domainName) { + return routesOperations.getRoutes(domainName); + } + + @Override + public UUID getRequiredServiceInstanceGuid(String name) { + return serviceInstancesOperations.getRequiredServiceInstanceGuid(name); + } + + @Override + public CloudServiceInstance getServiceInstance(String serviceInstanceName) { + return serviceInstancesOperations.getServiceInstance(serviceInstanceName); + } + + @Override + public CloudServiceInstance getServiceInstance(String serviceInstanceName, boolean required) { + return serviceInstancesOperations.getServiceInstance(serviceInstanceName, required); + } + + @Override + public String getServiceInstanceName(UUID serviceInstanceGuid) { + return serviceInstancesOperations.getServiceInstanceName(serviceInstanceGuid); + } + + @Override + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName) { + return serviceInstancesOperations.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); + } + + @Override + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName, boolean required) { + return serviceInstancesOperations.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName, required); + } + + @Override + public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { + return serviceBindingsOperations.getServiceBinding(serviceBindingGuid); + } + + @Override + public List getServiceAppBindings(UUID serviceInstanceGuid) { + return serviceBindingsOperations.getServiceAppBindings(serviceInstanceGuid); + } + + @Override + public List getAppBindings(UUID applicationGuid) { + return serviceBindingsOperations.getAppBindings(applicationGuid); + } + + @Override + public List getServiceBindingsForApplication(UUID applicationId, UUID serviceInstanceGuid) { + return serviceBindingsOperations.getServiceBindingsForApplication(applicationId, serviceInstanceGuid); + } + + @Override + public CloudServiceBroker getServiceBroker(String name) { + return serviceBrokersOperations.getServiceBroker(name); + } + + @Override + public CloudServiceBroker getServiceBroker(String name, boolean required) { + return serviceBrokersOperations.getServiceBroker(name, required); + } + + @Override + public List getServiceBrokers() { + return serviceBrokersOperations.getServiceBrokers(); + } + + @Override + public CloudServiceKey getServiceKey(String serviceInstanceName, String serviceKeyName) { + return serviceKeysOperations.getServiceKey(getServiceInstance(serviceInstanceName), serviceKeyName); + } + + @Override + public List getServiceKeys(String serviceInstanceName) { + return serviceKeysOperations.getServiceKeys(getServiceInstance(serviceInstanceName)); + } + + @Override + public List getServiceKeysWithCredentials(String serviceInstanceName) { + return serviceKeysOperations.getServiceKeysWithCredentials(getServiceInstance(serviceInstanceName)); + } + + @Override + public List getServiceKeys(CloudServiceInstance serviceInstance) { + return serviceKeysOperations.getServiceKeys(serviceInstance); + } + + @Override + public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) { + return serviceKeysOperations.getServiceKeysWithCredentials(serviceInstance); + } + + @Override + public List getServiceOfferings() { + return serviceOfferingsOperations.getServiceOfferings(); + } + + @Override + public List getSharedDomains() { + return domainsOperations.getSharedDomains(); + } + + @Override + public CloudStack getStack(String name) { + return stacksOperations.getStack(name); + } + + @Override + public CloudStack getStack(String name, boolean required) { + return stacksOperations.getStack(name, required); + } + + @Override + public List getStacks() { + return stacksOperations.getStacks(); + } + + @Override + public void startApplication(String applicationName) { + applicationsOperations.startApplication(applicationName); + } + + @Override + public void restartApplication(String applicationName) { + stopApplication(applicationName); + startApplication(applicationName); + } + + @Override + public void stopApplication(String applicationName) { + applicationsOperations.stopApplication(applicationName); + } + + @Override + public void rename(String applicationName, String newName) { + applicationsOperations.rename(applicationName, newName); + } + + @Override + public void updateApplicationInstances(String applicationName, int instances) { + applicationsOperations.updateApplicationInstances(applicationName, instances); + } + + @Override + public void updateApplicationMemory(String applicationName, int memory) { + applicationsOperations.updateApplicationMemory(applicationName, memory); + } + + @Override + public void updateApplicationDiskQuota(String applicationName, int disk) { + applicationsOperations.updateApplicationDiskQuota(applicationName, disk); + } + + @Override + public void updateApplicationEnv(String applicationName, Map env) { + applicationsOperations.updateApplicationEnv(applicationName, env); + } + + @Override + public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { + applicationsOperations.bindDropletToApp(dropletGuid, applicationGuid); + } + + @Override + public List unbindServiceInstance(String applicationName, String serviceInstanceName) { + return serviceBindingsOperations.unbindServiceInstance(applicationName, serviceInstanceName); + } + + @Override + public List unbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { + return serviceBindingsOperations.unbindServiceInstance(applicationGuid, serviceInstanceGuid); + } + + @Override + public void updateApplicationStaging(String applicationName, Staging staging) { + processesOperations.updateApplicationStaging(applicationName, staging); + } + + @Override + public void updateApplicationRoutes(String applicationName, Set routes) { + routesOperations.updateApplicationRoutes(applicationName, routes); + } + + @Override + public String updateServiceBroker(CloudServiceBroker serviceBroker) { + return serviceBrokersOperations.updateServiceBroker(serviceBroker); + } + + @Override + public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { + serviceBrokersOperations.updateServicePlanVisibilityForBroker(name, visibility); + } + + @Override + public void updateServicePlan(String serviceName, String planName) { + serviceInstancesOperations.updateServicePlan(serviceName, planName); + } + + @Override + public void updateServiceParameters(String serviceName, Map parameters) { + serviceInstancesOperations.updateServiceParameters(serviceName, parameters); + } + + @Override + public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) { + serviceInstancesOperations.updateServiceSyslogDrainUrl(serviceName, syslogDrainUrl); + } + + @Override + public void updateServiceTags(String serviceName, List tags) { + serviceInstancesOperations.updateServiceTags(serviceName, tags); + } + + @Override + public CloudPackage asyncUploadApplication(String applicationName, Path file, UploadStatusCallback callback, Duration uploadTimeout) { + return packagesOperations.asyncUploadApplication(applicationName, file, callback, uploadTimeout); + } + + @Override + public Upload getUploadStatus(UUID packageGuid) { + return packagesOperations.getUploadStatus(packageGuid); + } + + @Override + public CloudTask getTask(UUID taskGuid) { + return tasksOperations.getTask(taskGuid); + } + + @Override + public List getTasks(String applicationName) { + return tasksOperations.getTasks(applicationName); + } + + @Override + public CloudTask runTask(String applicationName, CloudTask task) { + return tasksOperations.runTask(applicationName, task); + } + + @Override + public CloudTask cancelTask(UUID taskGuid) { + return tasksOperations.cancelTask(taskGuid); + } + + @Override + public CloudBuild createBuild(UUID packageGuid) { + return buildsOperations.createBuild(packageGuid); + } + + @Override + public CloudBuild getBuild(UUID packageGuid) { + return buildsOperations.getBuild(packageGuid); + } + + @Override + public List getBuildsForApplication(UUID applicationGuid) { + return buildsOperations.getBuildsForApplication(applicationGuid); + } + + @Override + public Map getServiceInstanceParameters(UUID guid) { + return serviceInstancesOperations.getServiceInstanceParameters(guid); + } + + @Override + public Map getUserProvidedServiceInstanceParameters(UUID guid) { + return serviceInstancesOperations.getUserProvidedServiceInstanceParameters(guid); + } + + @Override + public Map getServiceBindingParameters(UUID guid) { + return serviceBindingsOperations.getServiceBindingParameters(guid); + } + + @Override + public List getBuildsForPackage(UUID packageGuid) { + return buildsOperations.getBuildsForPackage(packageGuid); + } + + @Override + public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { + return serviceInstancesOperations.getServiceInstancesWithoutAuxiliaryContentByNames(names); + } + + @Override + public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { + return serviceInstancesOperations.getServiceInstancesByMetadataLabelSelector(labelSelector); + } + + @Override + public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { + return serviceInstancesOperations.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(labelSelector); + } + + @Override + public void updateApplicationMetadata(UUID guid, Metadata metadata) { + applicationsOperations.updateApplicationMetadata(guid, metadata); + } + + @Override + public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { + serviceInstancesOperations.updateServiceInstanceMetadata(guid, metadata); + } + + @Override + public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { + serviceBindingsOperations.updateServiceBindingMetadata(guid, metadata); + } + + @Override + public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { + return processesOperations.getCurrentDropletForApplication(applicationGuid); + } + + @Override + public CloudPackage getPackage(UUID packageGuid) { + return packagesOperations.getPackage(packageGuid); + } + + @Override + public List getPackagesForApplication(UUID applicationGuid) { + return packagesOperations.getPackagesForApplication(applicationGuid); + } + + @Override + public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { + return rolesOperations.getUserRolesBySpaceAndUser(spaceGuid, userGuid); + } + + @Override + public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { + return packagesOperations.createDockerPackage(applicationGuid, dockerInfo); + } + + @Override + public CloudAsyncJob getAsyncJob(String jobId) { + return jobsOperations.getAsyncJob(jobId); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRootDocumentClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRootDocumentClient.java new file mode 100644 index 0000000000..5bcd1a6bf4 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRootDocumentClient.java @@ -0,0 +1,72 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URISyntaxException; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.text.MessageFormat; +import java.util.Map; + +import org.cloudfoundry.multiapps.common.util.MiscUtil; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudException; +import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; + +public class CloudControllerRootDocumentClient { + + private CloudControllerRootDocumentClient() { + } + + public static Map getRootDocumentLinks(URL controllerUrl, Map requestTags) { + try (HttpClient httpClient = createHttpClient()) { + HttpRequest request = buildRootDocumentRequest(controllerUrl, requestTags); + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + return extractRootDocumentLinks(controllerUrl, response); + } catch (InterruptedException e) { + Thread.currentThread() + .interrupt(); + + throw new CloudException(MessageFormat.format(Messages.INTERRUPTED_WHILE_CALLING_THE_CF_ROOT_URL_AT_0, controllerUrl), e); + } catch (Exception e) { + throw new CloudException(MessageFormat.format(Messages.FAILED_TO_CALL_THE_CF_ROOT_AT_0_WITH_1, controllerUrl, e.getMessage()), + e); + } + } + + private static HttpRequest buildRootDocumentRequest(URL controllerUrl, Map requestTags) throws URISyntaxException { + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .GET() + .uri(controllerUrl.toURI()) + .timeout(Constants.DEFAULT_CONNECT_TIMEOUT); + + requestTags.forEach(requestBuilder::header); + + return requestBuilder.build(); + } + + private static Map extractRootDocumentLinks(URL controllerUrl, HttpResponse response) { + if (!isSuccessful(response.statusCode())) { + throw new CloudException( + MessageFormat.format(Messages.CF_ROOT_DOCUMENT_REQUEST_TO_0_RETURNED_1, controllerUrl, response.statusCode())); + } + + return MiscUtil.cast(JsonUtil.convertJsonToMap(response.body()) + .get(Constants.ROOT_DOCUMENT_LINKS_LIST)); + } + + private static boolean isSuccessful(int statusCode) { + return statusCode >= 200 && statusCode < 300; + } + + private static HttpClient createHttpClient() { + return HttpClient.newBuilder() + .connectTimeout(Constants.DEFAULT_CONNECT_TIMEOUT) + .build(); + } + +} + + diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java new file mode 100644 index 0000000000..7b323b36aa --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java @@ -0,0 +1,299 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.text.MessageFormat; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.cloudfoundry.multiapps.common.util.MiscUtil; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudException; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Flux; + +public class CloudControllerV3Client { + + private final RestClient restClient; + + private final WebClient webClient; + + public CloudControllerV3Client(RestClient restClient) { + this(restClient, null); + } + + public CloudControllerV3Client(RestClient restClient, WebClient webClient) { + this.restClient = restClient; + this.webClient = webClient; + } + + public T get(String uri, Class responseType) { + return restClient.get() + .uri(uri) + .retrieve() + .body(responseType); + } + + public T postForObject(String uri, Object body, Class responseType, Object... uriVars) { + RestClient.RequestBodySpec spec = restClient.post() + .uri(uri, uriVars); + if (body != null) { + spec.body(body); + } + return spec.retrieve() + .body(responseType); + } + + public ResponseEntity post(String uri, Object body, Object... uriVars) { + RestClient.RequestBodySpec spec = restClient.post() + .uri(uri, uriVars); + if (body != null) { + spec.body(body); + } + return spec.retrieve() + .toBodilessEntity(); + } + + public ResponseEntity patch(String uri, Object body, Object... uriVars) { + return restClient.patch() + .uri(uri, uriVars) + .body(body) + .retrieve() + .toBodilessEntity(); + } + + public ResponseEntity delete(String uri, Object... uriVars) { + return restClient.delete() + .uri(uri, uriVars) + .retrieve() + .toBodilessEntity(); + } + + private T get(String uri, ParameterizedTypeReference responseType) { + return restClient.get() + .uri(uri) + .retrieve() + .body(responseType); + } + + public Optional getOptional(String uri, Class responseType) { + try { + return Optional.ofNullable(get(uri, responseType)); + } catch (CloudOperationException e) { + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } + throw e; + } + } + + public List list(String firstPageUri, ParameterizedTypeReference> pageType) { + if (firstPageUri == null) { + return new ArrayList<>(); + } + + String firstPageRelativeUri = toRelativeIfAbsolute(firstPageUri); + + V3ListResponse firstPage = get(firstPageRelativeUri, pageType); + + if (firstPage == null) { + return new ArrayList<>(); + } + + Integer totalPages = firstPage.pagination() == null ? null + : firstPage.pagination() + .totalPages(); + + if (webClient == null || totalPages == null || totalPages <= 1) { + return listSequentially(firstPage, pageType); + } + + List resultFromAllPages = new ArrayList<>(firstPage.resources()); + resultFromAllPages.addAll(fetchRemainingPagesConcurrently(firstPageRelativeUri, totalPages, pageType)); + return resultFromAllPages; + } + + private List listSequentially(V3ListResponse firstPage, ParameterizedTypeReference> pageType) { + List resultFromAllPages = new ArrayList<>(firstPage.resources()); + String nextUri = toRelativeIfAbsolute(firstPage.nextPageHref()); + + while (nextUri != null) { + V3ListResponse currentPage = get(nextUri, pageType); + + if (currentPage == null) { + break; + } + + resultFromAllPages.addAll(currentPage.resources()); + nextUri = toRelativeIfAbsolute(currentPage.nextPageHref()); + } + + return resultFromAllPages; + } + + private List fetchRemainingPagesConcurrently(String firstPageRelativeUri, int totalPages, + ParameterizedTypeReference> pageType) { + return Flux.range(Constants.SECOND_PAGE, totalPages - 1) + .flatMapSequential(page -> webClient.get() + .uri(pageUri(firstPageRelativeUri, page)) + .retrieve() + .bodyToMono(pageType) + .onErrorMap(WebClientResponseException.class, this::toCloudOperationException), + Constants.MAX_CONCURRENT_PAGES) + .flatMapIterable(V3ListResponse::resources) + .collectList() + .block(); + } + + static String pageUri(String firstPageRelativeUri, int page) { + return UriComponentsBuilder.fromUriString(firstPageRelativeUri) + .replaceQueryParam("page", page) + .build() + .toUriString(); + } + + private CloudOperationException toCloudOperationException(WebClientResponseException e) { + HttpStatus status = HttpStatus.valueOf(e.getStatusCode() + .value()); + + return new CloudOperationException(status, status.getReasonPhrase(), e.getResponseBodyAsString(), e); + } + + public void followAsyncJob(ResponseEntity acceptedResponse, Duration timeout) { + URI location = acceptedResponse.getHeaders() + .getLocation(); + + if (location == null) { + return; // when location is null, the job has already completed and we can return immediately + } + + waitForAsyncJob(extractJobGuid(location.toString()), timeout); + } + + public V3Job waitForAsyncJob(String jobGuid, Duration timeout) { + long startTimeNanos = System.nanoTime(); + long timeoutNanos = timeout.toNanos(); + Duration interval = Constants.JOB_POLL_MIN_INTERVAL; + + while (true) { + V3Job asyncJob = get(CloudControllerV3Endpoints.JOBS + jobGuid, V3Job.class); + + if (asyncJob != null && asyncJob.isTerminal()) { + + if (asyncJob.isFailed()) { + throw jobFailed(asyncJob); + } + + return asyncJob; + } + + long elapsedNanos = determineElapsedTime(startTimeNanos, timeout, jobGuid); + long remainingNanos = timeoutNanos - elapsedNanos; + + Duration sleepDuration = interval.toNanos() > remainingNanos ? Duration.ofNanos(remainingNanos) : interval; + sleep(sleepDuration); + + interval = nextInterval(interval); + } + } + + private static Duration nextInterval(Duration current) { + Duration increasedInterval = current.multipliedBy(Constants.JOB_POLL_INTERVAL_MULTIPLIER); + + return increasedInterval.compareTo(Constants.JOB_POLL_MAX_INTERVAL) > 0 ? Constants.JOB_POLL_MAX_INTERVAL : increasedInterval; + } + + private static void sleep(Duration interval) { + try { + MiscUtil.sleep(interval.toMillis()); + } catch (IllegalStateException e) { + throw new CloudException(MessageFormat.format(Messages.INTERRUPTED_WHILE_POLLING_ASYNC_JOB_0, e.getMessage()), e); + } + } + + private static CloudOperationException jobFailed(V3Job job) { + String detail = Messages.JOB_FAILED_WITHOUT_DETAILS; + + if (job.errors() != null && !job.errors() + .isEmpty()) { + detail = job.errors() + .stream() + .map(CloudControllerV3Client::extractErrorMessage) + .reduce((combinedMessages, nextMessage) -> combinedMessages + Constants.NEW_LINE + nextMessage) + .orElse(detail); + } + + String message = MessageFormat.format(Messages.JOB_0_WITH_OPERATION_1_FAILED_WITH_2, job.guid(), job.operation(), detail); + return new CloudOperationException(HttpStatus.UNPROCESSABLE_ENTITY, Messages.JOB_FAILED, message); + } + + private static String extractErrorMessage(V3Job.V3Error error) { + if (error.detail() != null) { + return error.detail(); + } + + if (error.title() != null) { + return error.title(); + } + + return Messages.JOB_FAILED_WITHOUT_DETAILS; + } + + private static String extractJobGuid(String location) { + int index = location.lastIndexOf(CloudControllerV3Endpoints.JOBS); + + if (index < 0) { + return location.substring(location.lastIndexOf('/') + 1); + } + + return location.substring(index + CloudControllerV3Endpoints.JOBS.length()); + } + + private String toRelativeIfAbsolute(String href) { + if (href == null) { + return null; + } + + URI uri = URI.create(href); + + if (uri.isAbsolute()) { + String pathAndQuery = uri.getRawPath(); + + if (uri.getRawQuery() != null) { + pathAndQuery = pathAndQuery + "?" + uri.getRawQuery(); + } + + return pathAndQuery; + } + + return href; + } + + private long determineElapsedTime(long startTimeNanos, Duration timeout, String jobGuid) { + long elapsedNanos = System.nanoTime() - startTimeNanos; + long timeoutNanos = timeout.toNanos(); + + if (elapsedNanos >= timeoutNanos) { + throw new CloudOperationException(HttpStatus.GATEWAY_TIMEOUT, Messages.JOB_TIMEOUT, + MessageFormat.format(Messages.JOB_0_DID_NOT_COMPLETE_WITHIN_1, jobGuid, timeout)); + } + + return elapsedNanos; + } + + public RestClient getRestClient() { + return restClient; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java new file mode 100644 index 0000000000..835bbf9a8c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java @@ -0,0 +1,73 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +public class CloudControllerV3Endpoints { + + //leaving it like that for testing, then if all is alright with the performance, we can remove it and just not pass it since the default is 50 from cf controller side + public static final int DEFAULT_PAGE_SIZE = 50; + + public static final String APPS = "/v3/apps"; + public static final String ROUTES = "/v3/routes"; + public static final String DOMAINS = "/v3/domains"; + public static final String SERVICE_INSTANCES = "/v3/service_instances"; + public static final String SERVICE_CREDENTIAL_BINDINGS = "/v3/service_credential_bindings"; + public static final String SERVICE_BROKERS = "/v3/service_brokers"; + public static final String SERVICE_OFFERINGS = "/v3/service_offerings"; + public static final String SERVICE_PLANS = "/v3/service_plans"; + public static final String PACKAGES = "/v3/packages"; + public static final String BUILDS = "/v3/builds"; + public static final String TASKS = "/v3/tasks"; + public static final String STACKS = "/v3/stacks"; + public static final String AUDIT_EVENTS = "/v3/audit_events"; + public static final String JOBS = "/v3/jobs"; + public static final String ROLES = "/v3/roles"; + public static final String ORGANIZATIONS = "/v3/organizations"; + public static final String SPACES = "/v3/spaces"; + + public static final String APP_BY_GUID = "/v3/apps/{guid}"; + public static final String APP_START = "/v3/apps/{guid}/actions/start"; + public static final String APP_STOP = "/v3/apps/{guid}/actions/stop"; + public static final String APP_ENV_VARS = "/v3/apps/{guid}/environment_variables"; + public static final String APP_FEATURE = "/v3/apps/{guid}/features/{name}"; + public static final String APP_WEB_PROCESS_SCALE = "/v3/apps/{guid}/processes/web/actions/scale"; + public static final String APP_CURRENT_DROPLET = "/v3/apps/{guid}/relationships/current_droplet"; + public static final String APP_TASKS = "/v3/apps/{guid}/tasks"; + public static final String ROUTE_BY_GUID = "/v3/routes/{guid}"; + public static final String ROUTE_DESTINATIONS = "/v3/routes/{guid}/destinations"; + public static final String ROUTE_DESTINATION_BY_GUID = "/v3/routes/{routeGuid}/destinations/{destinationGuid}"; + public static final String DOMAIN_BY_GUID = "/v3/domains/{guid}"; + public static final String SERVICE_INSTANCE_BY_GUID = "/v3/service_instances/{guid}"; + public static final String SERVICE_BROKER_BY_GUID = "/v3/service_brokers/{guid}"; + public static final String SERVICE_CREDENTIAL_BINDING_BY_GUID = "/v3/service_credential_bindings/{guid}"; + public static final String SERVICE_PLAN_VISIBILITY = "/v3/service_plans/{guid}/visibility"; + public static final String PROCESS_BY_GUID = "/v3/processes/{guid}"; + public static final String TASK_CANCEL = "/v3/tasks/{guid}/actions/cancel"; + public static final String PACKAGE_UPLOAD = "/v3/packages/{guid}/upload"; + public static final String SPACE_UNMAPPED_ROUTES = "/v3/spaces/{guid}/routes?unmapped=true"; + public static final String ORGANIZATION_DEFAULT_DOMAIN = "/v3/organizations/{guid}/domains/default"; + public static final String ORGANIZATION_DOMAINS = "/v3/organizations/{guid}/domains"; + public static final String ORGANIZATION_BY_GUID = "/v3/organizations/{guid}"; + public static final String SPACE_BY_GUID = "/v3/spaces/{guid}"; + public static final String JOB_BY_GUID = "/v3/jobs/{guid}"; + public static final String BUILD_BY_GUID = "/v3/builds/{guid}"; + public static final String PACKAGE_BY_GUID = "/v3/packages/{guid}"; + public static final String TASK_BY_GUID = "/v3/tasks/{guid}"; + public static final String SERVICE_PLAN_BY_GUID = "/v3/service_plans/{guid}"; + public static final String SERVICE_OFFERING_BY_GUID = "/v3/service_offerings/{guid}"; + public static final String SERVICE_INSTANCE_PARAMETERS = "/v3/service_instances/{guid}/parameters"; + public static final String SERVICE_INSTANCE_CREDENTIALS = "/v3/service_instances/{guid}/credentials"; + public static final String SERVICE_CREDENTIAL_BINDING_PARAMETERS = "/v3/service_credential_bindings/{guid}/parameters"; + public static final String SERVICE_CREDENTIAL_BINDING_DETAILS = "/v3/service_credential_bindings/{guid}/details"; + public static final String APP_WEB_PROCESS = "/v3/apps/{guid}/processes/web"; + public static final String APP_WEB_PROCESS_STATS = "/v3/apps/{guid}/processes/web/stats"; + public static final String APP_SSH_ENABLED = "/v3/apps/{guid}/ssh_enabled"; + public static final String APP_FEATURES = "/v3/apps/{guid}/features"; + public static final String APP_DROPLET_CURRENT = "/v3/apps/{guid}/droplets/current"; + public static final String APP_BUILDS = "/v3/apps/{guid}/builds"; + public static final String APP_PACKAGES = "/v3/apps/{guid}/packages"; + public static final String APP_ROUTES = "/v3/apps/{guid}/routes"; + + private CloudControllerV3Endpoints() { + + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java index 0eb52d8e0d..b97a881557 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java @@ -1,127 +1,100 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.RawCloudEntity; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudOrganization; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.util.UriUtil; - -import org.cloudfoundry.AbstractCloudFoundryException; -import org.cloudfoundry.client.v3.ClientV3Exception; -import org.cloudfoundry.client.v3.organizations.GetOrganizationRequest; -import org.cloudfoundry.client.v3.organizations.ListOrganizationsRequest; -import org.cloudfoundry.client.v3.organizations.Organization; -import org.cloudfoundry.client.v3.organizations.OrganizationsV3; -import org.cloudfoundry.client.v3.spaces.GetSpaceRequest; -import org.cloudfoundry.client.v3.spaces.ListSpacesRequest; -import org.cloudfoundry.client.v3.spaces.Space; -import org.cloudfoundry.client.v3.spaces.SpacesV3; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Organization; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ResourceMappers; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Space; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; -import reactor.util.retry.Retry; -import reactor.util.retry.RetryBackoffSpec; - -import java.time.Duration; -import java.util.List; -import java.util.UUID; public class CloudSpaceClient { - private static final List CHARS_TO_ENCODE = List.of(","); - private static final long RETRIES = 3; - private static final Duration RETRY_INTERVAL = Duration.ofSeconds(3); + private final CloudControllerV3Client client; - private final SpacesV3 spacesClient; - private final OrganizationsV3 orgsClient; - - public CloudSpaceClient(SpacesV3 spacesClient, OrganizationsV3 orgsClient) { - this.spacesClient = spacesClient; - this.orgsClient = orgsClient; + public CloudSpaceClient(CloudControllerV3Client client) { + this.client = client; } public CloudSpace getSpace(UUID spaceGuid) { - Space space = spacesClient.get(GetSpaceRequest.builder() - .spaceId(spaceGuid.toString()) - .build()) - .retryWhen(Retry.fixedDelay(RETRIES, RETRY_INTERVAL) - .onRetryExhaustedThrow(this::throwOriginalError)) - .onErrorMap(ClientV3Exception.class, this::convertV3ClientException) - .block(); - if (space == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Space with GUID " + spaceGuid + " not found."); - } + V3Space space = client.getOptional(V3Uri.path(CloudControllerV3Endpoints.SPACE_BY_GUID, spaceGuid) + .build(), V3Space.class) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SPACE_WITH_GUID_0_NOT_FOUND, + spaceGuid))); + + String orgGuid = space.organizationGuid(); + V3Organization org = client.getOptional(V3Uri.path(CloudControllerV3Endpoints.ORGANIZATION_BY_GUID, orgGuid) + .build(), V3Organization.class) + .orElseThrow( + () -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format( + Messages.ORGANIZATION_WITH_GUID_0_NOT_FOUND, + orgGuid))); - String orgGuid = space.getRelationships() - .getOrganization() - .getData() - .getId(); - Organization org = orgsClient.get(GetOrganizationRequest.builder() - .organizationId(orgGuid) - .build()) - .retryWhen(Retry.fixedDelay(RETRIES, RETRY_INTERVAL) - .onRetryExhaustedThrow(this::throwOriginalError)) - .onErrorMap(ClientV3Exception.class, this::convertV3ClientException) - .block(); - if (org == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Organization with GUID " + orgGuid + " not found."); - } return mapToCloudSpace(space, org); } public CloudSpace getSpace(String organizationName, String spaceName) { - var orgsResponse = orgsClient.list(ListOrganizationsRequest.builder() - .name(encodeAsQueryParam(organizationName)) - .build()) - .retryWhen(Retry.fixedDelay(RETRIES, RETRY_INTERVAL) - .onRetryExhaustedThrow(this::throwOriginalError)) - .onErrorMap(ClientV3Exception.class, this::convertV3ClientException) - .block(); - List orgs = orgsResponse.getResources(); - if (orgs.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Organization " + organizationName + " not found."); - } + V3Organization organization = findOrganizationByName(organizationName); + V3Space space = findSpaceByName(spaceName, organization); - Organization org = orgs.get(0); - - var spacesResponse = spacesClient.list(ListSpacesRequest.builder() - .organizationId(org.getId()) - .name(encodeAsQueryParam(spaceName)) - .build()) - .retryWhen(Retry.fixedDelay(RETRIES, RETRY_INTERVAL) - .onRetryExhaustedThrow(this::throwOriginalError)) - .onErrorMap(ClientV3Exception.class, this::convertV3ClientException) - .block(); - List spaces = spacesResponse.getResources(); - if (spaces.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Space " + spaceName + " not found in organization " + organizationName); + return mapToCloudSpace(space, organization); + } + + private V3Organization findOrganizationByName(String organizationName) { + List organizations = client.list(V3Uri.path(CloudControllerV3Endpoints.ORGANIZATIONS) + .queryEncoded(V3QueryParameters.NAMES, organizationName) + .build(), + new ParameterizedTypeReference>() { + }); + + if (organizations.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.ORGANISATION_0_NOT_FOUND, organizationName)); } - Space space = spaces.get(0); - return mapToCloudSpace(space, org); + return organizations.getFirst(); } - private String encodeAsQueryParam(String param) { - return UriUtil.encodeChars(param, CHARS_TO_ENCODE); + private V3Space findSpaceByName(String spaceName, V3Organization org) { + List spaces = client.list( + V3Uri.path(CloudControllerV3Endpoints.SPACES) + .query(V3QueryParameters.ORGANIZATION_GUIDS, org.guid()) + .queryEncoded(V3QueryParameters.NAMES, spaceName) + .build(), + new ParameterizedTypeReference>() { + }); + + if (spaces.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SPACE_0_NOT_FOUND_IN_ORGANIZATION_1, spaceName, + org.name())); + } + + return spaces.getFirst(); } - private CloudSpace mapToCloudSpace(Space space, Organization org) { + private CloudSpace mapToCloudSpace(V3Space space, V3Organization org) { return ImmutableCloudSpace.builder() - .metadata(RawCloudEntity.parseResourceMetadata(space)) - .name(space.getName()) + .metadata(V3ResourceMappers.parseMetadata(space.guid(), space.createdAt(), space.updatedAt())) + .name(space.name()) .organization(ImmutableCloudOrganization.builder() - .metadata(RawCloudEntity.parseResourceMetadata(org)) - .name(org.getName()) + .metadata(V3ResourceMappers.parseMetadata(org.guid(), + org.createdAt(), + org.updatedAt())) + .name(org.name()) .build()) .build(); } - private Throwable throwOriginalError(RetryBackoffSpec retrySpec, Retry.RetrySignal signal) { - return signal.failure(); - } - - private CloudOperationException convertV3ClientException(AbstractCloudFoundryException e) { - HttpStatus httpStatus = HttpStatus.valueOf(e.getStatusCode()); - return new CloudOperationException(httpStatus, httpStatus.getReasonPhrase(), e.getMessage(), e); - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java new file mode 100644 index 0000000000..fd8f18eef4 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java @@ -0,0 +1,146 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Predicate; + +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3DomainMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; + +public class DomainsV3Operations { + + private static final ParameterizedTypeReference> DOMAIN_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public DomainsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public void addDomain(String domainName) { + assertSpaceProvided("add domain"); + CloudDomain domain = findDomainByName(domainName); + + if (domain == null) { + createDomain(domainName); + } + + } + + public void deleteDomain(String domainName) { + assertSpaceProvided("delete domain"); + CloudDomain domain = findDomainByName(domainName); + + if (domain == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND, domainName)); + } + + deleteDomain(domain.getGuid()); + } + + public CloudDomain getDefaultDomain() { + UUID organizationGuid = OperationsUtil.getGuid(targetSpace.getOrganization()); + V3Domain domain = client.get(V3Uri.path(CloudControllerV3Endpoints.ORGANIZATION_DEFAULT_DOMAIN, organizationGuid) + .build(), + V3Domain.class); + + if (domain == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.DOMAIN_WITH_GUID_0_NOT_FOUND, organizationGuid)); + } + + return V3DomainMapper.toCloudDomain(domain); + } + + public List getDomains() { + return filterAndMapDomains(domain -> true); + } + + public List getSharedDomains() { + return filterAndMapDomains(domain -> !domain.isPrivate()); + } + + public List getPrivateDomains() { + return filterAndMapDomains(domain -> domain.isPrivate()); + } + + private List filterAndMapDomains(Predicate filterPredicate) { + return getAllDomains().stream() + .filter(filterPredicate) + .map(V3DomainMapper::toCloudDomain) + .toList(); + } + + public List getDomainsForOrganization() { + assertSpaceProvided("access organization domains"); + UUID targetOrganizationGuid = OperationsUtil.getGuid(targetSpace.getOrganization()); + + String uri = V3Uri.path(CloudControllerV3Endpoints.ORGANIZATION_DOMAINS, targetOrganizationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, DOMAIN_PAGE) + .stream() + .map(V3DomainMapper::toCloudDomain) + .toList(); + } + + private List getAllDomains() { + return client.list(V3Uri.path(CloudControllerV3Endpoints.DOMAINS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), DOMAIN_PAGE); + } + + private CloudDomain findDomainByName(String name) { + String uri = V3Uri.path(CloudControllerV3Endpoints.DOMAINS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, DOMAIN_PAGE) + .stream() + .findFirst() + .map(V3DomainMapper::toCloudDomain) + .orElse(null); + } + + private void createDomain(String name) { + UUID organizationGuid = OperationsUtil.getGuid(targetSpace.getOrganization()); + if (organizationGuid == null) { + throw new CloudOperationException(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.getReasonPhrase(), + MessageFormat.format(Messages.CANNOT_CREATE_DOMAIN_0_WITHOUT_ORGANIZATION, name)); + } + + Map organizationRelationship = OperationsUtil.organizationRelationship(organizationGuid); + client.post(CloudControllerV3Endpoints.DOMAINS, Map.of(V3Fields.NAME, name, V3Fields.RELATIONSHIPS, organizationRelationship)); + } + + private void deleteDomain(UUID guid) { + ResponseEntity response = client.delete(CloudControllerV3Endpoints.DOMAIN_BY_GUID, guid.toString()); + client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); + } + + private void assertSpaceProvided(String operation) { + Assert.notNull(targetSpace, "Unable to " + operation + " without specifying organization and space to use."); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java new file mode 100644 index 0000000000..978177ff19 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java @@ -0,0 +1,76 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEvent; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEventMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +public class EventsV3Operations { + + private static final ParameterizedTypeReference> EVENT_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public EventsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public List getEvents() { + return client.list(V3Uri.path(CloudControllerV3Endpoints.AUDIT_EVENTS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), EVENT_PAGE) + .stream() + .map(V3AuditEventMapper::toCloudEvent) + .toList(); + } + + public List getEventsByTarget(UUID uuid) { + String query = V3Uri.path(CloudControllerV3Endpoints.AUDIT_EVENTS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.TARGET_GUIDS, uuid) + .build(); + + return client.list(query, EVENT_PAGE) + .stream() + .map(V3AuditEventMapper::toCloudEvent) + .toList(); + } + + public List getApplicationEvents(String applicationName) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + + return getEventsByTarget(applicationGuid); + } + + private UUID getRequiredApplicationGuid(String applicationName) { + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = client.list(applicationsQuery, APP_PAGE); + + if (apps.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return UUID.fromString(apps.getFirst() + .guid()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/HttpConnectionPair.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/HttpConnectionPair.java new file mode 100644 index 0000000000..191f0e73ca --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/HttpConnectionPair.java @@ -0,0 +1,8 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URL; + +import reactor.netty.http.client.HttpClient; + +public record HttpConnectionPair(HttpClient httpClient, URL v3ApiUrl, URL logCacheUrl) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java new file mode 100644 index 0000000000..884890f031 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java @@ -0,0 +1,33 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3JobMapper; +import org.springframework.http.HttpStatus; + +public class JobsV3Operations { + + private final CloudControllerV3Client client; + + public JobsV3Operations(CloudControllerV3Client client) { + this.client = client; + } + + public CloudAsyncJob getAsyncJob(String jobId) { + V3Job job = client.get(V3Uri.path(CloudControllerV3Endpoints.JOB_BY_GUID, jobId) + .build(), + V3Job.class); + + if (job == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.JOB_WITH_GUID_0_NOT_FOUND, jobId)); + } + + return V3JobMapper.toCloudAsyncJob(job); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java new file mode 100644 index 0000000000..aada44b1b8 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java @@ -0,0 +1,48 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; + +public class OperationsQueryUtil { + + private OperationsQueryUtil() { + } + + public static String buildApplicationsQuery(String name, CloudSpace targetSpace) { + return buildApplicationsQuery(name, targetSpace, null); + } + + public static String buildApplicationsQuery(String name, CloudSpace targetSpace, String labelSelector) { + return V3Uri.path(CloudControllerV3Endpoints.APPS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, spaceGuidOrNull(targetSpace)) + .queryEncoded(V3QueryParameters.NAMES, name) + .query(V3QueryParameters.LABEL_SELECTOR, labelSelector) + .build(); + } + + public static String buildRoutesQuery(UUID domainGuid, String host, String path, UUID targetSpaceGuid) { + return V3Uri.path(CloudControllerV3Endpoints.ROUTES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.DOMAIN_GUIDS, domainGuid) + .query(V3QueryParameters.SPACE_GUIDS, targetSpaceGuid) + .query(V3QueryParameters.HOSTS, host) + .query(V3QueryParameters.PATHS, path) + .build(); + } + + public static String buildServiceInstancesQuery(String name, CloudSpace targetSpace) { + return V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, spaceGuidOrNull(targetSpace)) + .queryEncoded(V3QueryParameters.NAMES, name) + .build(); + } + + private static UUID spaceGuidOrNull(CloudSpace targetSpace) { + return targetSpace == null ? null : targetSpace.getGuid(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java new file mode 100644 index 0000000000..30f670e6ed --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java @@ -0,0 +1,493 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.common.util.MapUtil; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Droplet; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +public class OperationsUtil { + + private OperationsUtil() { + } + + public static Map buildApplicationBody(ApplicationToCreateDto dto, String targetSpaceGuid) { + Map body = new HashMap<>(); + body.put(V3Fields.NAME, dto.getName()); + body.put(V3Fields.LIFECYCLE, buildLifecycle(dto.getStaging())); + body.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.SPACE, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, targetSpaceGuid)))); + if (dto.getEnv() != null) { + body.put(V3Fields.ENVIRONMENT_VARIABLES, dto.getEnv()); + } + + if (dto.getMetadata() != null) { + body.put(V3Fields.METADATA, toMetadataMap(dto.getMetadata())); + } + + return body; + } + + private static Map buildLifecycle(Staging staging) { + if (staging == null) { + return Map.of(V3Fields.TYPE, V3Fields.BUILDPACK, V3Fields.DATA, Map.of()); + } + + if (staging.getDockerInfo() != null) { + return Map.of(V3Fields.TYPE, V3Fields.DOCKER, V3Fields.DATA, Map.of()); + } + + String type = staging.getLifecycleType() != null ? staging.getLifecycleType() + .name() + .toLowerCase() + : V3Fields.BUILDPACK; + + Map data = new HashMap<>(); + if (staging.getBuildpacks() != null) { + data.put(V3Fields.BUILDPACKS, staging.getBuildpacks()); + } + + if (staging.getStackName() != null) { + data.put(V3Fields.STACK, staging.getStackName()); + } + + return Map.of(V3Fields.TYPE, type, V3Fields.DATA, data); + } + + public static DropletInfo parseDropletInfo(V3Droplet droplet) { + String packageUrl = droplet.links() + .get(Constants.PACKAGE_LINK) + .href(); + + if (packageUrl.endsWith(Constants.SLASH)) { + packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf(Constants.SLASH)); + } + + String packageGuid = packageUrl.substring(packageUrl.lastIndexOf(Constants.SLASH) + 1); + return ImmutableDropletInfo.builder() + .guid(UUID.fromString(droplet.guid())) + .packageGuid(UUID.fromString(packageGuid)) + .build(); + } + + public static Map buildApplicationLifecycle(Staging staging) { + if (staging.getDockerInfo() != null) { + return Map.of(V3Fields.TYPE, V3Fields.DOCKER, V3Fields.DATA, Map.of()); + } + + String lifecycleType = staging.getLifecycleType() != null ? staging.getLifecycleType() + .name() + .toLowerCase() + : V3Fields.BUILDPACK; + + if (Constants.CLOUD_NATIVE_BUILDPACK.equals(lifecycleType) && (staging.getBuildpacks() == null || staging.getBuildpacks() + .isEmpty())) { + throw new IllegalArgumentException(Messages.BUILDPACKS_ARE_REQUIRED_FOR_THE_BUILDPACK_LIFECYCLE_TYPE); + } + + Map data = new HashMap<>(); + if (staging.getStackName() != null) { + data.put(V3Fields.STACK, staging.getStackName()); + } + + if (staging.getBuildpacks() != null) { + data.put(V3Fields.BUILDPACKS, staging.getBuildpacks()); + } + + return Map.of(V3Fields.TYPE, lifecycleType, V3Fields.DATA, data); + } + + private static Map buildHealthCheck(Staging staging) { + Map data = new HashMap<>(); + MapUtil.addNonNull(data, V3Fields.ENDPOINT, staging.getHealthCheckHttpEndpoint()); + MapUtil.addNonNull(data, V3Fields.TIMEOUT, staging.getHealthCheckTimeout()); + MapUtil.addNonNull(data, V3Fields.INVOCATION_TIMEOUT, staging.getInvocationTimeout()); + MapUtil.addNonNull(data, V3Fields.INTERVAL, staging.getHealthCheckInterval()); + + Map healthCheck = new HashMap<>(); + healthCheck.put(V3Fields.TYPE, staging.getHealthCheckType()); + healthCheck.put(V3Fields.DATA, data); + return healthCheck; + } + + private static Map buildReadinessHealthCheck(Staging staging) { + Map data = new HashMap<>(); + MapUtil.addNonNull(data, V3Fields.INVOCATION_TIMEOUT, staging.getReadinessHealthCheckInvocationTimeout()); + MapUtil.addNonNull(data, V3Fields.ENDPOINT, staging.getReadinessHealthCheckHttpEndpoint()); + MapUtil.addNonNull(data, V3Fields.INTERVAL, staging.getReadinessHealthCheckInterval()); + + Map readinessHealthCheck = new HashMap<>(); + readinessHealthCheck.put(V3Fields.TYPE, staging.getReadinessHealthCheckType()); + readinessHealthCheck.put(V3Fields.DATA, data); + return readinessHealthCheck; + } + + public static Map buildUpdatedProcessBody(Staging staging) { + Map updatedProcessBody = new HashMap<>(); + updatedProcessBody.put(V3Fields.COMMAND, staging.getCommand()); + + if (staging.getHealthCheckType() != null) { + updatedProcessBody.put(V3Fields.HEALTH_CHECK, buildHealthCheck(staging)); + } + + if (staging.getReadinessHealthCheckType() != null) { + updatedProcessBody.put(V3Fields.READINESS_HEALTH_CHECK, buildReadinessHealthCheck(staging)); + } + + return updatedProcessBody; + } + + public static Map buildCreateRouteBody(UUID domainGuid, String host, String path, UUID targetSpaceGuid) { + Map relationships = Map.of(V3Fields.DOMAIN, buildSingleResourceRelationship(domainGuid), + V3Fields.SPACE, buildSingleResourceRelationship(targetSpaceGuid)); + + return Map.of(V3Fields.HOST, host == null ? Constants.EMPTY_STRING : host, + V3Fields.PATH, path == null ? Constants.EMPTY_STRING : path, + V3Fields.RELATIONSHIPS, relationships); + } + + private static Map buildSingleResourceRelationship(UUID guid) { + return Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, guid.toString())); + } + + public static Map createDestination(UUID applicationGuid, String protocol) { + if (protocol == null) { + return Map.of(V3Fields.APP, Map.of(V3Fields.GUID, applicationGuid.toString())); + } + + return Map.of(V3Fields.APP, Map.of(V3Fields.GUID, applicationGuid.toString()), V3Fields.PROTOCOL, protocol); + } + + public static Set getOutdatedRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { + return currentRoutes.stream() + .filter(currentRoute -> isRouteOutdated(applicationGuid, currentRoute, updatedRoutes)) + .collect(Collectors.toSet()); + } + + private static boolean isRouteOutdated(UUID applicationGuid, CloudRoute currentRoute, Set updatedRoutes) { + Optional updatedRoute = findRoute(currentRoute.getUrl(), updatedRoutes); + + return updatedRoute.map(cloudRoute -> isProtocolChanged(applicationGuid, currentRoute, cloudRoute)) + .orElse(true); + + } + + public static Set getNewRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { + return updatedRoutes.stream() + .filter(updatedRoute -> isRouteUpdated(applicationGuid, updatedRoute, currentRoutes)) + .collect(Collectors.toSet()); + } + + private static boolean isRouteUpdated(UUID applicationGuid, CloudRoute updatedRoute, List currentRoutes) { + Optional currentRoute = findRoute(updatedRoute.getUrl(), currentRoutes); + + return currentRoute.map(cloudRoute -> isProtocolChanged(applicationGuid, cloudRoute, updatedRoute)) + .orElse(true); + + } + + private static Optional findRoute(String url, Collection routes) { + return routes.stream() + .filter(route -> Objects.equals(url, route.getUrl())) + .findFirst(); + } + + private static boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, CloudRoute updatedRoute) { + if (updatedRoute.getRequestedProtocol() == null) { + return false; + } + + return currentRoute.getDestinations() + .stream() + .filter(routeDestination -> Objects.equals(routeDestination.getApplicationGuid(), applicationGuid)) + .noneMatch(routeDestination -> Objects.equals(routeDestination.getProtocol(), + updatedRoute.getRequestedProtocol())); + } + + public static void validateDomainForRoute(CloudRoute route, Map existingDomains) { + String domain = route.getDomain() + .getName(); + + if (!StringUtils.hasLength(domain) || !existingDomains.containsKey(domain)) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND_FOR_URI_1, domain, route.getUrl())); + } + + } + + public static Map buildServiceInstanceBody(String bindingName, UUID applicationGuid, UUID serviceInstanceGuid, + Map parameters) { + Map body = new HashMap<>(); + body.put(V3Fields.NAME, bindingName); + body.put(V3Fields.TYPE, V3Fields.APP); + body.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.APP, toOneRelationship(applicationGuid), V3Fields.SERVICE_INSTANCE, + toOneRelationship(serviceInstanceGuid))); + + if (!CollectionUtils.isEmpty(parameters)) { + body.put(V3Fields.PARAMETERS, parameters); + } + + return body; + } + + private static Map toOneRelationship(UUID guid) { + return Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, guid.toString())); + } + + public static UUID getGuid(CloudEntity entity) { + if (entity == null || entity.getMetadata() == null) { + return null; + } + + return entity.getMetadata() + .getGuid(); + } + + public static Map organizationRelationship(UUID organizationGuid) { + return Map.of(V3Fields.ORGANIZATION, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, organizationGuid.toString()))); + } + + public static boolean isUploadReady(Status status) { + return status == Status.READY; + } + + public static boolean hasUploadFailed(Status status) { + return status == Status.EXPIRED || status == Status.FAILED; + } + + public static void throwOnErrors(List errors) { + if (errors.isEmpty()) { + return; + } + + CloudOperationException firstException = errors.getFirst(); + errors.subList(1, errors.size()) + .forEach(firstException::addSuppressed); + + throw firstException; + } + + public static Optional extractJobGuid(ResponseEntity response) { + URI location = response.getHeaders() + .getLocation(); + + if (location == null) { + return Optional.empty(); + } + + String path = location.getPath(); + return Optional.of(path.substring(path.lastIndexOf('/') + 1)); + } + + public static Map basicAuthentication(CloudServiceBroker serviceBroker) { + return Map.of(V3Fields.TYPE, V3Fields.BASIC, V3Fields.CREDENTIALS, + Map.of(V3Fields.USERNAME, nullToEmpty(serviceBroker.getUsername()), V3Fields.PASSWORD, + nullToEmpty(serviceBroker.getPassword()))); + } + + private static String nullToEmpty(String value) { + return value == null ? Constants.EMPTY_STRING : value; + } + + public static Map buildCreateServiceBrokerBody(CloudServiceBroker serviceBroker) { + Map body = new HashMap<>(); + Map basicAuthentication = OperationsUtil.basicAuthentication(serviceBroker); + + body.put(V3Fields.NAME, serviceBroker.getName()); + body.put(V3Fields.URL, serviceBroker.getUrl()); + body.put(V3Fields.AUTHENTICATION, basicAuthentication); + + if (serviceBroker.getSpaceGuid() != null) { + body.put(V3Fields.RELATIONSHIPS, + Map.of(V3Fields.SPACE, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, serviceBroker.getSpaceGuid())))); + } + + return body; + } + + public static Map buildCreateServiceInstanceBody(CloudServiceInstance serviceInstance, UUID servicePlanGuid, + CloudSpace targetSpace) { + Map body = new HashMap<>(); + body.put(V3Fields.TYPE, V3Fields.MANAGED); + body.put(V3Fields.NAME, serviceInstance.getName()); + MapUtil.addNonNull(body, V3Fields.METADATA, toMetadataMap(serviceInstance.getV3Metadata())); + MapUtil.addNonNull(body, V3Fields.TAGS, serviceInstance.getTags()); + MapUtil.addNonNull(body, V3Fields.PARAMETERS, serviceInstance.getCredentials()); + body.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.SERVICE_PLAN, toOneRelationship(servicePlanGuid.toString()), V3Fields.SPACE, + toOneRelationship(getGuid(targetSpace).toString()))); + + return body; + } + + public static Map toMetadataMap(Metadata metadata) { + if (metadata == null) { + return null; + } + + Map metadataMapResult = new HashMap<>(); + metadataMapResult.put(V3Fields.LABELS, metadata.getLabels() == null ? Map.of() : metadata.getLabels()); + metadataMapResult.put(V3Fields.ANNOTATIONS, metadata.getAnnotations() == null ? Map.of() : metadata.getAnnotations()); + + return metadataMapResult; + } + + public static Map toOneRelationship(String guid) { + return Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, guid)); + } + + public static Map buildCreateUserProvidedServiceInstance(CloudServiceInstance serviceInstance, String syslogDrainUrl, + CloudSpace targetSpace) { + Map body = new HashMap<>(); + body.put(V3Fields.TYPE, V3Fields.USER_PROVIDED); + body.put(V3Fields.NAME, serviceInstance.getName()); + MapUtil.addNonNull(body, V3Fields.METADATA, toMetadataMap(serviceInstance.getV3Metadata())); + MapUtil.addNonNull(body, V3Fields.CREDENTIALS, serviceInstance.getCredentials()); + body.put(V3Fields.SYSLOG_DRAIN_URL, syslogDrainUrl); + MapUtil.addNonNull(body, V3Fields.TAGS, serviceInstance.getTags()); + body.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.SPACE, toOneRelationship(getGuid(targetSpace).toString()))); + return body; + } + + public static boolean isUserProvided(V3ServiceInstance resource) { + return Constants.USER_PROVIDED.equals(resource.type()); + } + + public static String servicePlanGuidOf(V3ServiceInstance resource) { + if (resource.relationships() == null || resource.relationships() + .servicePlan() == null + || resource.relationships() + .servicePlan() + .data() == null) { + return null; + } + + return resource.relationships() + .servicePlan() + .data() + .guid(); + } + + public static CloudOperationException determineNameResolutionError(CloudOperationException e, String forbiddenMessage, + String notFoundMessage) { + if (e.getStatusCode() == HttpStatus.FORBIDDEN) { + return new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), forbiddenMessage, e); + } + + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { + return new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), notFoundMessage, e); + } + + return e; + } + + public static String getServiceOfferingGuidFromServicePlan(V3ServicePlan plan) { + if (plan.relationships() == null || plan.relationships() + .serviceOffering() == null + || plan.relationships() + .serviceOffering() + .data() == null) { + return null; + } + + return plan.relationships() + .serviceOffering() + .data() + .guid(); + } + + public static List> toBatches(Collection largeList, int maxCharLength) { + if (largeList.isEmpty()) { + return List.of(); + } + + List> batches = new ArrayList<>(); + int currentBatchLength = 0; + int currentBatchIndex = 0; + batches.add(new ArrayList<>()); + + for (T element : largeList) { + int elementLength = element.toString() + .length(); + + if (elementLength + currentBatchLength >= maxCharLength) { + batches.add(new ArrayList<>()); + currentBatchIndex++; + currentBatchLength = 0; + } + + batches.get(currentBatchIndex) + .add(element); + currentBatchLength += elementLength; + } + + return batches; + } + + public static Map buildCreateServiceKeyBody(String name, Map parameters, Metadata metadata, + UUID serviceInstanceGuid) { + Map resultBody = new HashMap<>(); + resultBody.put(V3Fields.TYPE, V3Fields.KEY); + resultBody.put(V3Fields.NAME, name); + resultBody.put(V3Fields.RELATIONSHIPS, + Map.of(V3Fields.SERVICE_INSTANCE, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, serviceInstanceGuid.toString())))); + + if (parameters != null && !parameters.isEmpty()) { + resultBody.put(V3Fields.PARAMETERS, parameters); + } + + MapUtil.addNonNull(resultBody, V3Fields.METADATA, toMetadataMap(metadata)); + + return resultBody; + } + + public static Map buildCreateTaskBody(CloudTask task) { + Map body = new HashMap<>(); + body.put(V3Fields.COMMAND, task.getCommand()); + body.put(V3Fields.NAME, task.getName()); + + CloudTask.Limits limits = task.getLimits(); + if (limits != null) { + if (limits.getMemory() != null) { + body.put(V3Fields.MEMORY_IN_MB, limits.getMemory()); + } + + if (limits.getDisk() != null) { + body.put(V3Fields.DISK_IN_MB, limits.getDisk()); + } + } + + return body; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java new file mode 100644 index 0000000000..1443af8a11 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java @@ -0,0 +1,224 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.nio.file.Path; +import java.text.MessageFormat; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.function.Function; + +import org.cloudfoundry.multiapps.common.util.MiscUtil; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; +import org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerCredentials; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ErrorDetails; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableErrorDetails; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableUpload; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Package; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3PackageMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; + +public class PackagesV3Operations { + + private static final ParameterizedTypeReference> PACKAGE_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + private final Function uploadRestClientFactory; + + private final ExecutorService uploadMonitorExecutor = createUploadMonitorExecutor(); + + public PackagesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this(client, targetSpace, null); + } + + public PackagesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace, + Function uploadRestClientFactory) { + this.client = client; + this.targetSpace = targetSpace; + this.uploadRestClientFactory = uploadRestClientFactory; + } + + public CloudPackage getPackage(UUID packageGuid) { + V3Package packageResource = client.get(V3Uri.path(CloudControllerV3Endpoints.PACKAGE_BY_GUID, packageGuid) + .build(), V3Package.class); + + if (packageResource == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.PACKAGE_0_NOT_FOUND, packageGuid)); + } + + return V3PackageMapper.toCloudPackage(packageResource); + } + + public List getPackagesForApplication(UUID applicationGuid) { + return client.list(V3Uri.path(CloudControllerV3Endpoints.APP_PACKAGES, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), PACKAGE_PAGE) + .stream() + .map(V3PackageMapper::toCloudPackage) + .toList(); + } + + public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { + Map data = new HashMap<>(); + data.put(V3Fields.IMAGE, dockerInfo.getImage()); + + DockerCredentials credentials = dockerInfo.getCredentials(); + if (credentials != null && credentials.getUsername() != null && credentials.getPassword() != null) { + data.put(V3Fields.USERNAME, credentials.getUsername()); + data.put(V3Fields.PASSWORD, credentials.getPassword()); + } + + V3Package created = client.postForObject(CloudControllerV3Endpoints.PACKAGES, + Map.of(V3Fields.TYPE, V3Fields.DOCKER, V3Fields.DATA, data, V3Fields.RELATIONSHIPS, + applicationRelationship(applicationGuid)), + V3Package.class); + + return getPackage(UUID.fromString(created.guid())); + } + + public Upload getUploadStatus(UUID packageGuid) { + CloudPackage cloudPackage = getPackage(packageGuid); + ErrorDetails errorDetails = null; + + if (cloudPackage.getType() == CloudPackage.Type.BITS) { + errorDetails = ImmutableErrorDetails.builder() + .description(((BitsData) cloudPackage.getData()).getError()) + .build(); + } + + return ImmutableUpload.builder() + .status(cloudPackage.getStatus()) + .errorDetails(errorDetails) + .build(); + } + + public CloudPackage asyncUploadApplication(String applicationName, Path file, UploadStatusCallback callback, Duration uploadTimeout) { + CloudPackage cloudPackage = startUpload(applicationName, file, uploadTimeout); + processAsyncUploadInBackground(cloudPackage, callback); + return cloudPackage; + } + + private CloudPackage startUpload(String applicationName, Path file, Duration uploadTimeout) { + Assert.notNull(applicationName, "AppName must not be null"); + Assert.notNull(file, "File must not be null"); + + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + UUID packageGuid = createBitsPackage(applicationGuid).getGuid(); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add(V3Fields.BITS, new FileSystemResource(file)); + + RestClient uploadClient = resolveUploadClient(uploadTimeout); + uploadClient.post() + .uri(CloudControllerV3Endpoints.PACKAGE_UPLOAD, packageGuid) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(body) + .retrieve() + .toBodilessEntity(); + + return getPackage(packageGuid); + } + + private RestClient resolveUploadClient(Duration uploadTimeout) { + if (uploadRestClientFactory != null && uploadTimeout != null && !uploadTimeout.isZero() && !uploadTimeout.isNegative()) { + return uploadRestClientFactory.apply(uploadTimeout); + } + + return client.getRestClient(); + } + + private CloudPackage createBitsPackage(UUID applicationGuid) { + V3Package created = client.postForObject(CloudControllerV3Endpoints.PACKAGES, + Map.of(V3Fields.TYPE, V3Fields.BITS, V3Fields.RELATIONSHIPS, + applicationRelationship(applicationGuid)), + V3Package.class); + + return getPackage(UUID.fromString(created.guid())); + } + + private static Map applicationRelationship(UUID applicationGuid) { + return Map.of(V3Fields.APP, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, applicationGuid.toString()))); + } + + private UUID getRequiredApplicationGuid(String applicationName) { + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = client.list(applicationsQuery, APP_PAGE); + + if (apps.isEmpty() || apps.getFirst() + .guid() == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return UUID.fromString(apps.getFirst() + .guid()); + } + + private void processAsyncUploadInBackground(CloudPackage cloudPackage, UploadStatusCallback callback) { + uploadMonitorExecutor.execute(() -> processAsyncUpload(cloudPackage, callback)); + } + + private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback callback) { + boolean keepPolling = true; + + while (keepPolling) { + Upload upload = getUploadStatus(cloudPackage.getGuid()); + Status uploadStatus = upload.getStatus(); + boolean unsubscribe = callback.onProgress(uploadStatus.toString()); + + if (unsubscribe || OperationsUtil.isUploadReady(uploadStatus)) { + keepPolling = false; + } else if (OperationsUtil.hasUploadFailed(uploadStatus)) { + callback.onError(upload.getErrorDetails() + .getDescription()); + keepPolling = false; + } else { + try { + MiscUtil.sleep(Constants.PACKAGE_UPLOAD_JOB_POLLING_PERIOD); + } catch (IllegalStateException e) { + keepPolling = false; + } + } + } + } + + private ExecutorService createUploadMonitorExecutor() { + ThreadFactory threadFactory = Thread.ofVirtual() + .name(Constants.UPLOAD_MONITOR_THREAD_NAME_PREFIX, 0) + .factory(); + + return Executors.newThreadPerTaskExecutor(threadFactory); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java new file mode 100644 index 0000000000..2d4a680215 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java @@ -0,0 +1,155 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AppFeature; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Droplet; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3InstancesInfoMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Process; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ProcessMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +public class ProcessesV3Operations { + + private static final ParameterizedTypeReference> APP_FEATURE_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public ProcessesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public CloudProcess getApplicationProcess(UUID applicationGuid) { + V3Process applicationProcess = client.get( + V3Uri.path(CloudControllerV3Endpoints.APP_WEB_PROCESS, applicationGuid) + .build(), + V3Process.class); + + return applicationProcess == null ? null : V3ProcessMapper.toCloudProcess(applicationProcess); + } + + public InstancesInfo getApplicationInstances(CloudApplication application) { + if (application.getState() + .equals(CloudApplication.State.STARTED)) { + return getApplicationInstances(application.getGuid()); + } + + return ImmutableInstancesInfo.builder() + .instances(Collections.emptyList()) + .build(); + } + + public InstancesInfo getApplicationInstances(UUID applicationGuid) { + V3Process.V3ProcessStats stats = client.get(V3Uri.path(CloudControllerV3Endpoints.APP_WEB_PROCESS_STATS, applicationGuid) + .build(), + V3Process.V3ProcessStats.class); + + if (stats == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.STATISTICS_FOR_APPLICATION_INSTANCES_WITH_GUID_0_NOT_FOUND, + applicationGuid)); + } + + return V3InstancesInfoMapper.toInstancesInfo(stats); + } + + public Map getApplicationFeatures(UUID applicationGuid) { + List applicationFeatures = client.list(V3Uri.path(CloudControllerV3Endpoints.APP_FEATURES, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), + APP_FEATURE_PAGE); + + Map result = new HashMap<>(); + for (V3AppFeature feature : applicationFeatures) { + result.put(feature.name(), feature.enabled()); + } + + return result; + } + + public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { + V3Droplet currentDroplet = client.get(V3Uri.path(CloudControllerV3Endpoints.APP_DROPLET_CURRENT, applicationGuid) + .build(), + V3Droplet.class); + + if (currentDroplet == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.APPLICATION_WITH_GUID_0_DOES_NOT_HAVE_A_DROPLET, + applicationGuid)); + } + + return OperationsUtil.parseDropletInfo(currentDroplet); + } + + public void updateApplicationStaging(String applicationName, Staging staging) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + Map applicationLifecycle = OperationsUtil.buildApplicationLifecycle(staging); + + client.patch(CloudControllerV3Endpoints.APP_BY_GUID, Map.of(V3Fields.LIFECYCLE, applicationLifecycle), applicationGuid); + updateApplicationProcess(applicationGuid, staging); + } + + private void updateApplicationProcess(UUID applicationGuid, Staging staging) { + staging.getAppFeatures() + .forEach((featureName, enabled) -> updateAppFeature(applicationGuid, featureName, enabled)); + + V3Process process = client.get( + V3Uri.path(CloudControllerV3Endpoints.APP_WEB_PROCESS, applicationGuid) + .build(), + V3Process.class); + + if (process == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.APPLICATION_PROCESS_FOR_APPLICATION_WITH_GUID_0_NOT_FOUND, + applicationGuid)); + } + + Map updateProcessBody = OperationsUtil.buildUpdatedProcessBody(staging); + + client.patch(CloudControllerV3Endpoints.PROCESS_BY_GUID, updateProcessBody, process.guid()); + } + + private void updateAppFeature(UUID applicationGuid, String featureName, boolean enabled) { + client.patch(CloudControllerV3Endpoints.APP_FEATURE, Map.of(V3Fields.ENABLED, enabled), applicationGuid, featureName); + } + + private UUID getRequiredApplicationGuid(String applicationName) { + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + + List apps = client.list(applicationsQuery, APP_PAGE); + + if (apps.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return UUID.fromString(apps.getFirst() + .guid()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java new file mode 100644 index 0000000000..5743f7be7d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java @@ -0,0 +1,33 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.function.Function; + +import org.cloudfoundry.multiapps.controller.Constants; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +final class ReactiveFanOut { + + private ReactiveFanOut() { + } + + static List mapConcurrently(List items, Function mapper) { + if (items.isEmpty()) { + return List.of(); + } + + if (items.size() == 1) { + return List.of(mapper.apply(items.getFirst())); + } + + return Flux.fromIterable(items) + .flatMapSequential(item -> Mono.fromCallable(() -> mapper.apply(item)) + .subscribeOn(Schedulers.boundedElastic()), + Constants.DEFAULT_CONCURRENT_TASKS) + .collectList() + .block(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java new file mode 100644 index 0000000000..4aece63b19 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java @@ -0,0 +1,39 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.EnumSet; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Role; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3RoleMapper; +import org.springframework.core.ParameterizedTypeReference; + +public class RolesV3Operations { + + private static final ParameterizedTypeReference> ROLE_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + public RolesV3Operations(CloudControllerV3Client client) { + this.client = client; + } + + public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.ROLES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, spaceGuid) + .query(V3QueryParameters.USER_GUIDS, userGuid) + .build(); + + return client.list(uri, ROLE_PAGE) + .stream() + .map(V3RoleMapper::toUserRole) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(UserRole.class))); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java new file mode 100644 index 0000000000..832c7adbe6 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java @@ -0,0 +1,268 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3RouteMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +public class RoutesV3Operations { + + private static final ParameterizedTypeReference> ROUTE_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> DOMAIN_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public RoutesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public void addRoute(String host, String domainName, String path) { + assertSpaceProvided("add route for domain"); + UUID domainGuid = getRequiredDomainGuid(domainName); + addRoute(domainGuid, host, path); + } + + public void deleteRoute(String host, String domainName, String path) { + assertSpaceProvided("delete route for domain"); + + UUID routeGuid = getRouteGuid(getRequiredDomainGuid(domainName), host, path); + if (routeGuid == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.HOST_0_NOT_FOUND_FOR_DOMAIN_1, host, domainName)); + } + + deleteRoute(routeGuid); + } + + public void deleteOrphanedRoutes() { + ResponseEntity response = client.delete(CloudControllerV3Endpoints.SPACE_UNMAPPED_ROUTES, getTargetSpaceGuid()); + + client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); + } + + public List getRoutes(String domainName) { + assertSpaceProvided("get routes for domain"); + UUID domainGuid = getRequiredDomainGuid(domainName); + + return findRoutesByDomainGuid(domainGuid); + } + + public List getApplicationRoutes(UUID applicationGuid) { + return listRoutes(V3Uri.path(CloudControllerV3Endpoints.APP_ROUTES, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build()).stream() + .map(route -> V3RouteMapper.toCloudRoute( + route, applicationGuid)) + .toList(); + } + + public void updateApplicationRoutes(String applicationName, Set updatedRoutes) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + + List appRoutes = listRoutes( + V3Uri.path(CloudControllerV3Endpoints.APP_ROUTES, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build()).stream() + .map(V3RouteMapper::toCloudRoute) + .toList(); + + Set outdatedRoutes = OperationsUtil.getOutdatedRoutes(applicationGuid, appRoutes, updatedRoutes); + Set newRoutes = OperationsUtil.getNewRoutes(applicationGuid, appRoutes, updatedRoutes); + + removeRoutes(outdatedRoutes, applicationGuid); + addRoutes(newRoutes, applicationGuid); + } + + private List findRoutesByDomainGuid(UUID domainGuid) { + UUID targetSpaceGuid = getTargetSpaceGuid(); + String routeQuery = OperationsQueryUtil.buildRoutesQuery(domainGuid, null, null, targetSpaceGuid); + + return listRoutes(routeQuery).stream() + .map(V3RouteMapper::toCloudRoute) + .toList(); + } + + private UUID getRouteGuid(UUID domainGuid, String host, String path) { + UUID targetSpaceGuid = getTargetSpaceGuid(); + String routeQuery = OperationsQueryUtil.buildRoutesQuery(domainGuid, host, path, targetSpaceGuid); + + List routes = listRoutes(routeQuery); + if (CollectionUtils.isEmpty(routes)) { + return null; + } + + return UUID.fromString(routes.getFirst() + .guid()); + } + + private List listRoutes(String query) { + return client.list(query, ROUTE_PAGE); + } + + private void addRoutes(Set routes, UUID applicationGuid) { + Map domains = getDomainsFromRoutes(routes); + + for (CloudRoute route : routes) { + OperationsUtil.validateDomainForRoute(route, domains); + + UUID domainGuid = domains.get(route.getDomain() + .getName()); + UUID routeGuid = getOrAddRoute(domainGuid, route.getHost(), route.getPath()); + + bindRoute(routeGuid, applicationGuid, route.getRequestedProtocol()); + } + } + + private void removeRoutes(Set routes, UUID applicationGuid) { + for (CloudRoute route : routes) { + unbindRouteFromDestinationApplications(route, applicationGuid); + } + } + + private void unbindRouteFromDestinationApplications(CloudRoute route, UUID applicationGuid) { + List destinationsForCurrentRoute = route.getDestinations(); + + for (RouteDestination destination : destinationsForCurrentRoute) { + if (destination.getApplicationGuid() + .equals(applicationGuid)) { + unbindRoute(route.getGuid(), destination.getGuid()); + } + } + } + + private UUID getOrAddRoute(UUID domainGuid, String host, String path) { + UUID routeGuid = getRouteGuid(domainGuid, host, path); + + if (routeGuid == null) { + routeGuid = addRoute(domainGuid, host, path); + } + + return routeGuid; + } + + private UUID addRoute(UUID domainGuid, String host, String path) { + assertSpaceProvided("add route"); + UUID targetSpaceGuid = getTargetSpaceGuid(); + Map createRouteBody = OperationsUtil.buildCreateRouteBody(domainGuid, host, path, targetSpaceGuid); + + V3Route created = client.postForObject(CloudControllerV3Endpoints.ROUTES, createRouteBody, V3Route.class); + + return UUID.fromString(created.guid()); + } + + private void deleteRoute(UUID guid) { + ResponseEntity response = client.delete(CloudControllerV3Endpoints.ROUTE_BY_GUID, guid); + + client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); + } + + private void bindRoute(UUID routeGuid, UUID applicationGuid, String protocol) { + client.post(CloudControllerV3Endpoints.ROUTE_DESTINATIONS, + Map.of(V3Fields.DESTINATIONS, List.of(OperationsUtil.createDestination(applicationGuid, protocol))), routeGuid); + } + + private void unbindRoute(UUID routeGuid, UUID destinationGuid) { + client.delete(CloudControllerV3Endpoints.ROUTE_DESTINATION_BY_GUID, routeGuid, destinationGuid); + } + + private UUID getRequiredDomainGuid(String name) { + UUID guid = findDomainGuidByName(name); + + if (guid == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND, name)); + } + + return guid; + } + + private UUID findDomainGuidByName(String name) { + List domains = client.list(V3Uri.path(CloudControllerV3Endpoints.DOMAINS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), + DOMAIN_PAGE); + + if (CollectionUtils.isEmpty(domains)) { + return null; + } + + return UUID.fromString(domains.getFirst() + .guid()); + } + + private Map getDomainsFromRoutes(Set routes) { + Set domainNames = routes.stream() + .map(route -> route.getDomain() + .getName()) + .filter(StringUtils::hasLength) + .collect(Collectors.toSet()); + + if (domainNames.isEmpty()) { + return Map.of(); + } + + return client.list(V3Uri.path(CloudControllerV3Endpoints.DOMAINS) + .query(V3QueryParameters.NAMES, domainNames) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), DOMAIN_PAGE) + .stream() + .collect(Collectors.toMap(V3Domain::name, domain -> UUID.fromString(domain.guid()))); + } + + private UUID getRequiredApplicationGuid(String applicationName) { + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = client.list(applicationsQuery, + APP_PAGE); + + if (CollectionUtils.isEmpty(apps)) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return UUID.fromString(apps.getFirst() + .guid()); + } + + private UUID getTargetSpaceGuid() { + return targetSpace.getGuid(); + } + + private void assertSpaceProvided(String operation) { + if (targetSpace == null) { + throw new IllegalArgumentException( + MessageFormat.format(Messages.UNABLE_TO_0_WITHOUT_SPECIFYING_ORGANIZATION_AND_SPACE_TO_USE, operation)); + } + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java new file mode 100644 index 0000000000..b20e70a7a8 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java @@ -0,0 +1,210 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.cloudfoundry.multiapps.common.util.MiscUtil; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBindingMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class ServiceBindingsV3Operations { + + private static final ParameterizedTypeReference> BINDING_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> GUID_REFERENCE_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public ServiceBindingsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName) { + return bindServiceInstance(bindingName, applicationName, serviceInstanceName, null); + } + + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, + Map parameters) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); + Map serviceInstanceBody = OperationsUtil.buildServiceInstanceBody(bindingName, applicationGuid, serviceInstanceGuid, + parameters); + + ResponseEntity response = client.post(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS, serviceInstanceBody); + + return OperationsUtil.extractJobGuid(response); + } + + public List unbindServiceInstance(String applicationName, String serviceInstanceName) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); + + return unbindServiceInstance(applicationGuid, serviceInstanceGuid); + } + + public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.GUIDS, serviceBindingGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, BINDING_PAGE) + .stream() + .findFirst() + .map(V3ServiceBindingMapper::toCloudServiceBinding) + .orElse(null); + } + + public List getServiceAppBindings(UUID serviceInstanceGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .query(V3QueryParameters.TYPE, V3Fields.APP) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return listBindings(uri); + } + + public List getAppBindings(UUID applicationGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.APP_GUIDS, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return listBindings(uri); + } + + public List getServiceBindingsForApplication(UUID applicationId, UUID serviceInstanceGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.APP_GUIDS, applicationId) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return listBindings(uri); + } + + public Map getServiceBindingParameters(UUID guid) { + Map parameters = MiscUtil.cast( + client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_PARAMETERS, guid) + .build(), + Map.class)); + + if (parameters == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.PARAMETERS_OF_SERVICE_BINDING_WITH_GUID_0_NOT_FOUND, + guid)); + } + + return parameters; + } + + public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { + Map metadataMap = OperationsUtil.toMetadataMap(metadata); + client.patch(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, + Map.of(V3Fields.METADATA, metadataMap == null ? Map.of() : metadataMap), guid); + } + + private List listBindings(String uri) { + return client.list(uri, BINDING_PAGE) + .stream() + .map(V3ServiceBindingMapper::toCloudServiceBinding) + .toList(); + } + + public List unbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { + List serviceBindingGuids = getServiceBindingGuids(applicationGuid, serviceInstanceGuid); + + if (serviceBindingGuids.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format( + Messages.SERVICE_BINDING_BETWEEN_SERVICE_WITH_GUID_0_AND_APPLICATION_WITH_GUID_1_NOT_FOUND, serviceInstanceGuid, + applicationGuid)); + } + + return deleteServiceBindings(serviceBindingGuids); + } + + private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInstanceGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.APP_GUIDS, applicationGuid) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, BINDING_PAGE) + .stream() + .map(binding -> UUID.fromString(binding.guid())) + .toList(); + } + + private List deleteServiceBindings(List guids) { + List jobIds = new ArrayList<>(); + List errors = new ArrayList<>(); + + for (UUID guid : guids) { + try { + deleteServiceBinding(guid).ifPresent(jobIds::add); + } catch (CloudOperationException e) { + errors.add(e); + } + } + + OperationsUtil.throwOnErrors(errors); + return jobIds; + } + + public Optional deleteServiceBinding(UUID guid) { + ResponseEntity response = client.delete(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid); + + return OperationsUtil.extractJobGuid(response); + } + + private UUID getRequiredApplicationGuid(String applicationName) { + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + + List apps = client.list(applicationsQuery, GUID_REFERENCE_PAGE); + + if (apps.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return UUID.fromString(apps.getFirst() + .guid()); + } + + private UUID getRequiredServiceInstanceGuid(String name) { + String serviceInstancesQuery = OperationsQueryUtil.buildServiceInstancesQuery(name, targetSpace); + List instances = client.list(serviceInstancesQuery, GUID_REFERENCE_PAGE); + + if (instances.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_INSTANCE_0_NOT_FOUND, name)); + } + + return UUID.fromString(instances.getFirst() + .guid()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java new file mode 100644 index 0000000000..e57323eb02 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java @@ -0,0 +1,165 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBrokerMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; + +public class ServiceBrokersV3Operations { + + private static final ParameterizedTypeReference> BROKER_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> GUID_REFERENCE_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public ServiceBrokersV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public String createServiceBroker(CloudServiceBroker serviceBroker) { + Assert.notNull(serviceBroker, "Service broker must not be null."); + + Map body = OperationsUtil.buildCreateServiceBrokerBody(serviceBroker); + ResponseEntity response = client.post(CloudControllerV3Endpoints.SERVICE_BROKERS, body); + + return OperationsUtil.extractJobGuid(response) + .orElse(null); + } + + public String deleteServiceBroker(String name) { + CloudServiceBroker broker = getServiceBroker(name); + UUID guid = broker.getMetadata() + .getGuid(); + + ResponseEntity response = client.delete(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, guid.toString()); + + return OperationsUtil.extractJobGuid(response) + .orElse(null); + } + + public CloudServiceBroker getServiceBroker(String name) { + return getServiceBroker(name, true); + } + + public CloudServiceBroker getServiceBroker(String name, boolean required) { + CloudServiceBroker serviceBroker = findServiceBrokerByName(name); + + if (serviceBroker == null && required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_BROKER_0_NOT_FOUND, name)); + } + + return serviceBroker; + } + + public List getServiceBrokers() { + return client.list(V3Uri.path(CloudControllerV3Endpoints.SERVICE_BROKERS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), BROKER_PAGE) + .stream() + .map(V3ServiceBrokerMapper::toCloudServiceBroker) + .toList(); + } + + public String updateServiceBroker(CloudServiceBroker serviceBroker) { + Assert.notNull(serviceBroker, "Service broker must not be null."); + + CloudServiceBroker existingBroker = getServiceBroker(serviceBroker.getName()); + UUID brokerGuid = existingBroker.getMetadata() + .getGuid(); + + Map basicAuthentication = OperationsUtil.basicAuthentication(serviceBroker); + Map body = Map.of(V3Fields.NAME, serviceBroker.getName(), V3Fields.URL, serviceBroker.getUrl(), + V3Fields.AUTHENTICATION, basicAuthentication); + + ResponseEntity response = client.patch(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, body, brokerGuid.toString()); + + return OperationsUtil.extractJobGuid(response) + .orElse(null); + } + + public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { + CloudServiceBroker broker = getServiceBroker(name); + UUID brokerGuid = broker.getMetadata() + .getGuid(); + + for (UUID servicePlanGuid : findServicePlanGuidsByBrokerGuid(brokerGuid)) { + updateServicePlanVisibility(servicePlanGuid, visibility); + } + } + + private CloudServiceBroker findServiceBrokerByName(String name) { + return client.list( + V3Uri.path(CloudControllerV3Endpoints.SERVICE_BROKERS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), BROKER_PAGE) + .stream() + .findFirst() + .map(V3ServiceBrokerMapper::toCloudServiceBroker) + .orElse(null); + } + + private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { + return findServiceOfferingGuidsByBrokerGuid(brokerGuid).stream() + .flatMap(offeringGuid -> findServicePlanGuidsByOfferingGuid( + offeringGuid).stream()) + .toList(); + } + + private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERINGS) + .query(V3QueryParameters.SERVICE_BROKER_GUIDS, brokerGuid) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, GUID_REFERENCE_PAGE) + .stream() + .map(V3GuidReference::guid) + .map(UUID::fromString) + .toList(); + } + + private List findServicePlanGuidsByOfferingGuid(UUID serviceOfferingGuid) { + String uri = + V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLANS) + .query(V3QueryParameters.SERVICE_OFFERING_GUIDS, serviceOfferingGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, GUID_REFERENCE_PAGE) + .stream() + .map(V3GuidReference::guid) + .map(UUID::fromString) + .toList(); + } + + private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { + client.patch(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, Map.of(V3Fields.TYPE, visibility.toString()), + servicePlanGuid.toString()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java new file mode 100644 index 0000000000..3298f7b4b0 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -0,0 +1,376 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstanceMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +public class ServiceInstancesV3Operations { + + private static final ParameterizedTypeReference> SERVICE_INSTANCE_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> GUID_REFERENCE_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public ServiceInstancesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public void createServiceInstance(CloudServiceInstance serviceInstance) { + assertSpaceProvided("create service instance"); + Assert.notNull(serviceInstance, "Service instance must not be null."); + UUID servicePlanGuid = findPlanGuidForService(serviceInstance, serviceInstance.getPlan()); + + Map body = OperationsUtil.buildCreateServiceInstanceBody(serviceInstance, servicePlanGuid, targetSpace); + + client.post(CloudControllerV3Endpoints.SERVICE_INSTANCES, body); + } + + public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { + assertSpaceProvided("create service instance"); + Assert.notNull(serviceInstance, "Service instance must not be null."); + String syslogDrainUrl = StringUtils.hasText(serviceInstance.getSyslogDrainUrl()) + ? serviceInstance.getSyslogDrainUrl() + : Constants.EMPTY_STRING; + + Map body = OperationsUtil.buildCreateUserProvidedServiceInstance(serviceInstance, syslogDrainUrl, targetSpace); + + client.post(CloudControllerV3Endpoints.SERVICE_INSTANCES, body); + } + + public void deleteServiceInstance(String serviceInstanceName) { + CloudServiceInstance serviceInstance = getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); + deleteServiceInstance(serviceInstance.getGuid()); + } + + public void deleteServiceInstance(CloudServiceInstance serviceInstance) { + deleteServiceInstance(serviceInstance.getGuid()); + } + + public UUID getRequiredServiceInstanceGuid(String name) { + V3ServiceInstance resource = findServiceInstanceResourceByName(name); + + if (resource == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_INSTANCE_0_NOT_FOUND, name)); + } + + return UUID.fromString(resource.guid()); + } + + public CloudServiceInstance getServiceInstance(String serviceInstanceName) { + return getServiceInstance(serviceInstanceName, true); + } + + public CloudServiceInstance getServiceInstance(String serviceInstanceName, boolean required) { + CloudServiceInstance serviceInstance = findServiceInstanceByName(serviceInstanceName); + + return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); + } + + public String getServiceInstanceName(UUID serviceInstanceGuid) { + V3ServiceInstance serviceInstance = client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, serviceInstanceGuid) + .build(), + V3ServiceInstance.class); + + return serviceInstance == null ? null : serviceInstance.name(); + } + + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName) { + return getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName, true); + } + + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName, boolean required) { + V3ServiceInstance resource = findServiceInstanceResourceByName(serviceInstanceName); + + CloudServiceInstance serviceInstance = resource == null ? null : V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, + null); + + return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); + } + + public Map getServiceInstanceParameters(UUID guid) { + Map parameters = client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCE_PARAMETERS, guid) + .build(), + Map.class); + + if (parameters == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.PARAMETERS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND, + guid)); + } + + return parameters; + } + + public Map getUserProvidedServiceInstanceParameters(UUID guid) { + Map credentials = client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCE_CREDENTIALS, guid) + .build(), + Map.class); + + if (credentials == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.CREDENTIALS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND, + guid)); + } + + return credentials; + } + + public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { + List allServiceInstances = new ArrayList<>(); + + for (List batch : OperationsUtil.toBatches(names, Constants.MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.NAMES, batch) + .build(); + + client.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .map(V3ServiceInstanceMapper::toCloudServiceInstanceWithoutAuxiliaryContent) + .forEach(allServiceInstances::add); + } + + return allServiceInstances; + } + + public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.LABEL_SELECTOR, labelSelector) + .build(); + + return ReactiveFanOut.mapConcurrently(client.list(uri, SERVICE_INSTANCE_LIST_TYPE), this::mapWithSupportingContent); + } + + public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.LABEL_SELECTOR, labelSelector) + .build(); + + return client.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .map(resource -> V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null)) + .toList(); + } + + public void updateServicePlan(String serviceName, String planName) { + CloudServiceInstance service = getServiceInstance(serviceName); + + if (service.isUserProvided()) { + return; + } + + UUID planGuid = findPlanGuidForService(service, planName); + patchServiceInstance(service.getGuid(), + Map.of(V3Fields.RELATIONSHIPS, + Map.of(V3Fields.SERVICE_PLAN, OperationsUtil.toOneRelationship(planGuid.toString())))); + } + + public void updateServiceParameters(String serviceName, Map parameters) { + CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); + String key = service.isUserProvided() ? V3Fields.CREDENTIALS : V3Fields.PARAMETERS; + patchServiceInstance(service.getGuid(), Map.of(key, parameters)); + } + + public void updateServiceTags(String serviceName, List tags) { + UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceName); + patchServiceInstance(serviceInstanceGuid, Map.of(V3Fields.TAGS, tags)); + } + + public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) { + CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); + + if (!service.isUserProvided()) { + return; + } + + String updatedSyslogDrain = StringUtils.hasText(syslogDrainUrl) ? syslogDrainUrl : Constants.EMPTY_STRING; + patchServiceInstance(service.getGuid(), Map.of(V3Fields.SYSLOG_DRAIN_URL, updatedSyslogDrain)); + } + + public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { + Map metadataMap = OperationsUtil.toMetadataMap(metadata); + patchServiceInstance(guid, Map.of(V3Fields.METADATA, metadataMap == null ? Map.of() : metadataMap)); + } + + private CloudServiceInstance getServiceInstanceIfRequired(String serviceInstanceName, CloudServiceInstance serviceInstance, + boolean required) { + if (serviceInstance == null && required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_INSTANCE_0_NOT_FOUND, serviceInstanceName)); + } + + return serviceInstance; + } + + private CloudServiceInstance findServiceInstanceByName(String name) { + V3ServiceInstance resource = findServiceInstanceResourceByName(name); + + if (resource == null) { + return null; + } + + return mapWithSupportingContent(resource); + } + + private CloudServiceInstance mapWithSupportingContent(V3ServiceInstance resource) { + if (OperationsUtil.isUserProvided(resource)) { + return V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); + } + + String servicePlanGuid = OperationsUtil.servicePlanGuidOf(resource); + if (servicePlanGuid == null) { + return V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); + } + + ServicePlanNames names = resolvePlanAndOfferingNames(servicePlanGuid, resource.name()); + return V3ServiceInstanceMapper.toCloudServiceInstance(resource, names.planName(), names.offeringName()); + } + + private V3ServiceInstance findServiceInstanceResourceByName(String name) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.NAMES, name) + .build(); + + return client.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .findFirst() + .orElse(null); + } + + private ServicePlanNames resolvePlanAndOfferingNames(String servicePlanGuid, String serviceInstanceName) { + V3ServicePlan plan = getServicePlanForServiceInstance(servicePlanGuid, serviceInstanceName); + + if (plan == null) { + return new ServicePlanNames(null, null); + } + + String offeringName = null; + String offeringGuid = OperationsUtil.getServiceOfferingGuidFromServicePlan(plan); + + if (offeringGuid != null) { + V3ServiceOffering offering = getServiceOfferingForNameResolution(offeringGuid); + offeringName = offering == null ? null : offering.name(); + } + + return new ServicePlanNames(plan.name(), offeringName); + } + + private V3ServicePlan getServicePlanForServiceInstance(String servicePlanGuid, String serviceInstanceName) { + try { + return client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLAN_BY_GUID, servicePlanGuid) + .build(), + V3ServicePlan.class); + } catch (CloudOperationException e) { + throw OperationsUtil.determineNameResolutionError(e, + MessageFormat.format( + Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, + servicePlanGuid, serviceInstanceName), + MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, + serviceInstanceName)); + } + } + + private V3ServiceOffering getServiceOfferingForNameResolution(String offeringGuid) { + try { + return client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERING_BY_GUID, offeringGuid) + .build(), + V3ServiceOffering.class); + } catch (CloudOperationException e) { + throw OperationsUtil.determineNameResolutionError(e, + MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, + offeringGuid), + MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, + offeringGuid)); + } + } + + private UUID findPlanGuidForService(CloudServiceInstance service, String planName) { + List offeringGuids = findServiceOfferingGuids(service.getLabel(), service.getBroker()); + + for (String offeringGuid : offeringGuids) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLANS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SERVICE_OFFERING_GUIDS, offeringGuid) + .query(V3QueryParameters.NAMES, planName) + .build(); + + V3GuidReference plan = client.list(uri, GUID_REFERENCE_PAGE) + .stream() + .findFirst() + .orElse(null); + + if (plan != null) { + return UUID.fromString(plan.guid()); + } + } + + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_PLAN_0_NOT_FOUND, planName)); + } + + private List findServiceOfferingGuids(String label, String broker) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERINGS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.NAMES, label) + .query(V3QueryParameters.SERVICE_BROKER_NAMES, StringUtils.hasText(broker) ? broker : null) + .build(); + + return client.list(uri, GUID_REFERENCE_PAGE) + .stream() + .map(V3GuidReference::guid) + .toList(); + } + + private void deleteServiceInstance(UUID serviceInstanceGuid) { + client.delete(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, serviceInstanceGuid.toString()); + } + + private void patchServiceInstance(UUID guid, Map body) { + client.patch(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, body, guid.toString()); + } + + private void assertSpaceProvided(String operation) { + Assert.notNull(OperationsUtil.getGuid(targetSpace), + "Unable to " + operation + " without specifying organization and space to use."); + } + + private record ServicePlanNames(String planName, String offeringName) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java new file mode 100644 index 0000000000..b87bb38e01 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java @@ -0,0 +1,122 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.client.facade.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceKeyDetails; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceKeyMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.ResponseEntity; + +public class ServiceKeysV3Operations { + + private static final ParameterizedTypeReference> SERVICE_KEY_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + public ServiceKeysV3Operations(CloudControllerV3Client client) { + this.client = client; + } + + public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, CloudServiceInstance serviceInstance) { + ResponseEntity acceptedResponse = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), + serviceInstance); + + client.followAsyncJob(acceptedResponse, Constants.BINDING_OPERATIONS_TIMEOUT); + return getServiceKey(serviceInstance, keyModel.getName()); + } + + public Optional createServiceKey(CloudServiceKey keyModel, CloudServiceInstance serviceInstance) { + ResponseEntity acceptedResponse = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), + serviceInstance); + + return OperationsUtil.extractJobGuid(acceptedResponse); + } + + public Optional createServiceKey(CloudServiceInstance serviceInstance, String serviceKeyName, Map parameters) { + ResponseEntity acceptedResponse = postServiceKey(serviceKeyName, parameters, null, serviceInstance); + + return OperationsUtil.extractJobGuid(acceptedResponse); + } + + public CloudServiceKey getServiceKey(CloudServiceInstance serviceInstance, String serviceKeyName) { + V3ServiceBinding serviceKey = getServiceKeyResourceByNameAndServiceInstanceGuid(serviceKeyName, serviceInstance.getGuid()); + if (serviceKey == null) { + return null; + } + + Map credentials = getServiceKeyCredentials(serviceKey.guid()); + return V3ServiceKeyMapper.toCloudServiceKey(serviceKey, serviceInstance, credentials); + } + + public List getServiceKeys(CloudServiceInstance serviceInstance) { + return listServiceKeyResources(serviceInstance.getGuid()).stream() + .map(key -> V3ServiceKeyMapper.toCloudServiceKey(key, serviceInstance)) + .toList(); + } + + public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) { + return ReactiveFanOut.mapConcurrently(listServiceKeyResources(serviceInstance.getGuid()), + key -> V3ServiceKeyMapper.toCloudServiceKey(key, serviceInstance, + getServiceKeyCredentials(key.guid()))); + } + + private ResponseEntity postServiceKey(String name, Map parameters, Metadata metadata, + CloudServiceInstance serviceInstance) { + if (serviceInstance.getType() != ServiceInstanceType.MANAGED) { + throw new IllegalArgumentException( + String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, serviceInstance.getName())); + } + + return client.post(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS, + OperationsUtil.buildCreateServiceKeyBody(name, parameters, metadata, serviceInstance.getGuid())); + } + + private V3ServiceBinding getServiceKeyResourceByNameAndServiceInstanceGuid(String name, UUID serviceInstanceGuid) { + String query = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.TYPE, V3Fields.KEY) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .query(V3QueryParameters.NAMES, name) + .build(); + + List keys = client.list(query, SERVICE_KEY_PAGE); + + return keys.isEmpty() ? null : keys.getFirst(); + } + + private List listServiceKeyResources(UUID serviceInstanceGuid) { + String query = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.TYPE, V3Fields.KEY) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .build(); + + return client.list(query, SERVICE_KEY_PAGE); + } + + private Map getServiceKeyCredentials(String keyGuid) { + Optional details = client.getOptional(V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_DETAILS, + keyGuid) + .build(), + V3ServiceKeyDetails.class); + + return details.map(V3ServiceKeyDetails::credentials) + .orElse(Collections.emptyMap()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java new file mode 100644 index 0000000000..3c8a6c8840 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java @@ -0,0 +1,66 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOfferingMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlanMapper; +import org.springframework.core.ParameterizedTypeReference; + +public class ServiceOfferingsV3Operations { + + private static final ParameterizedTypeReference> OFFERING_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> PLAN_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public ServiceOfferingsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public List getServiceOfferings() { + return getServiceOfferingResources().stream() + .map(this::toCloudServiceOfferingWithPlans) + .toList(); + } + + private List getServiceOfferingResources() { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERINGS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .build(); + + return client.list(uri, OFFERING_PAGE); + } + + private CloudServiceOffering toCloudServiceOfferingWithPlans(V3ServiceOffering serviceOffering) { + List servicePlans = getServicePlans(serviceOffering.guid()); + + return V3ServiceOfferingMapper.toCloudServiceOffering(serviceOffering, servicePlans); + } + + private List getServicePlans(String serviceOfferingGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLANS) + .query(V3QueryParameters.SERVICE_OFFERING_GUIDS, serviceOfferingGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, PLAN_PAGE) + .stream() + .map(V3ServicePlanMapper::toCloudServicePlan) + .toList(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java new file mode 100644 index 0000000000..35d902cc36 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java @@ -0,0 +1,90 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +public class ServicePlansV3Operations { + + private static final ParameterizedTypeReference> GUID_REFERENCE_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public ServicePlansV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { + UUID brokerGuid = getRequiredServiceBrokerGuid(name); + List servicePlanGuids = findServicePlanGuidsByBrokerGuid(brokerGuid); + + for (UUID servicePlanGuid : servicePlanGuids) { + updateServicePlanVisibility(servicePlanGuid, visibility); + } + } + + private UUID getRequiredServiceBrokerGuid(String name) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_BROKERS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, GUID_REFERENCE_PAGE) + .stream() + .findFirst() + .map(broker -> UUID.fromString(broker.guid())) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_BROKER_0_NOT_FOUND, name))); + } + + private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { + List offeredServicesGuids = findServiceOfferingGuidsByBrokerGuid(brokerGuid); + if (offeredServicesGuids.isEmpty()) { + return List.of(); + } + + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLANS) + .query(V3QueryParameters.SERVICE_OFFERING_GUIDS, offeredServicesGuids) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); + + return client.list(uri, GUID_REFERENCE_PAGE) + .stream() + .map(plan -> UUID.fromString(plan.guid())) + .toList(); + } + + private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERINGS) + .query(V3QueryParameters.SERVICE_BROKER_GUIDS, brokerGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .build(); + + return client.list(uri, GUID_REFERENCE_PAGE) + .stream() + .map(offering -> UUID.fromString(offering.guid())) + .toList(); + } + + private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { + client.patch(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, Map.of(V3Fields.TYPE, visibility.toString()), servicePlanGuid); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java new file mode 100644 index 0000000000..9e788e4302 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java @@ -0,0 +1,62 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.List; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Stack; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3StackMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +public class StacksV3Operations { + + private static final ParameterizedTypeReference> STACK_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + public StacksV3Operations(CloudControllerV3Client client) { + this.client = client; + } + + public CloudStack getStack(String name) { + return getStack(name, true); + } + + public CloudStack getStack(String name, boolean required) { + CloudStack resultStack = findStackByName(name); + + if (resultStack == null && required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.STACK_0_NOT_FOUND, name)); + } + + return resultStack; + } + + public List getStacks() { + return client.list(V3Uri.path(CloudControllerV3Endpoints.STACKS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), STACK_PAGE) + .stream() + .map(V3StackMapper::toCloudStack) + .toList(); + } + + private CloudStack findStackByName(String name) { + return client.list(V3Uri.path(CloudControllerV3Endpoints.STACKS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), STACK_PAGE) + .stream() + .map(V3StackMapper::toCloudStack) + .findFirst() + .orElse(null); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java new file mode 100644 index 0000000000..587e1282ae --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java @@ -0,0 +1,87 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Task; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3TaskMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +public class TasksV3Operations { + + private static final ParameterizedTypeReference> TASK_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; + + public TasksV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; + } + + public CloudTask getTask(UUID taskGuid) { + V3Task task = client.get(V3Uri.path(CloudControllerV3Endpoints.TASK_BY_GUID, taskGuid) + .build(), V3Task.class); + + return task == null ? null : V3TaskMapper.toCloudTask(task); + } + + public List getTasks(String applicationName) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + + String query = V3Uri.path(CloudControllerV3Endpoints.TASKS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.APP_GUIDS, applicationGuid) + .build(); + + List tasks = client.list(query, TASK_PAGE); + + return tasks.stream() + .map(V3TaskMapper::toCloudTask) + .toList(); + } + + public CloudTask runTask(String applicationName, CloudTask task) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + Map createTaskBody = OperationsUtil.buildCreateTaskBody(task); + + V3Task created = client.postForObject(CloudControllerV3Endpoints.APP_TASKS, createTaskBody, V3Task.class, applicationGuid); + + return created == null ? null : V3TaskMapper.toCloudTask(created); + } + + public CloudTask cancelTask(UUID taskGuid) { + V3Task cancelled = client.postForObject(CloudControllerV3Endpoints.TASK_CANCEL, null, V3Task.class, taskGuid); + + return cancelled == null ? null : V3TaskMapper.toCloudTask(cancelled); + } + + private UUID getRequiredApplicationGuid(String applicationName) { + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = client.list(applicationsQuery, APP_PAGE); + + if (apps.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return UUID.fromString(apps.getFirst() + .guid()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java new file mode 100644 index 0000000000..6970f5ebb0 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java @@ -0,0 +1,95 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.client.facade.util.UriUtil; + +public final class V3Uri { + + private static final List CHARS_TO_ENCODE = List.of(Constants.COMMA); + + private final String path; + + private final List queryParameters = new ArrayList<>(); + + private V3Uri(String path) { + this.path = path; + } + + public static V3Uri path(String template, Object... args) { + return new V3Uri(fillPlaceholders(template, args)); + } + + public V3Uri perPage(int size) { + queryParameters.add(Constants.PER_PAGE + size); + return this; + } + + public V3Uri query(String key, Object value) { + if (value != null) { + queryParameters.add(key + Constants.EQUALS_SYMBOL + value); + } + + return this; + } + + public V3Uri queryEncoded(String key, Object value) { + if (value != null) { + queryParameters.add(key + Constants.EQUALS_SYMBOL + encodeCommas(String.valueOf(value))); + } + + return this; + } + + public V3Uri query(String key, Collection values) { + if (values != null && !values.isEmpty()) { + queryParameters.add(key + Constants.EQUALS_SYMBOL + values.stream() + .map(String::valueOf) + .map(V3Uri::encodeCommas) + .collect(Collectors.joining(Constants.COMMA))); + } + + return this; + } + + public String build() { + if (queryParameters.isEmpty()) { + return path; + } + + return path + Constants.QUESTION_MARK + String.join(Constants.AMPERSAND, queryParameters); + } + + private static String encodeCommas(String value) { + return UriUtil.encodeChars(value, CHARS_TO_ENCODE); + } + + private static String fillPlaceholders(String template, Object... args) { + StringBuilder result = new StringBuilder(); + + int argumentIndex = 0; + int nextSearchStart = 0; + int openBraceIndex; + + while ((openBraceIndex = template.indexOf(Constants.OPEN_BRACE_CHAR, nextSearchStart)) >= 0) { + int closeBraceIndex = template.indexOf(Constants.CLOSE_BRACE_CHAR, openBraceIndex); + + if (closeBraceIndex < 0) { + break; + } + + result.append(template, nextSearchStart, openBraceIndex) + .append(args[argumentIndex++]); + + nextSearchStart = closeBraceIndex + 1; + } + + result.append(template, nextSearchStart, template.length()); + return result.toString(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheFields.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheFields.java new file mode 100644 index 0000000000..4779017247 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheFields.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +public class LogCacheFields { + + public static final String ENVELOPES = "envelopes"; + public static final String BATCH = "batch"; + public static final String SOURCE_ID = "source_id"; + public static final String TIMESTAMP = "timestamp"; + public static final String TAGS = "tags"; + public static final String LOG = "log"; + public static final String PAYLOAD = "payload"; + public static final String TYPE = "type"; + + private LogCacheFields() { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java new file mode 100644 index 0000000000..469c646463 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java @@ -0,0 +1,34 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record LogCacheReadResponse(@JsonProperty(LogCacheFields.ENVELOPES) EnvelopeBatch envelopes) { + + public List batch() { + if (envelopes == null || envelopes.batch() == null) { + return Collections.emptyList(); + } + + return envelopes.batch(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record EnvelopeBatch(@JsonProperty(LogCacheFields.BATCH) List batch) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Envelope(@JsonProperty(LogCacheFields.SOURCE_ID) String sourceId, @JsonProperty(LogCacheFields.TIMESTAMP) Long timestamp, + @JsonProperty(LogCacheFields.TAGS) Map tags, @JsonProperty(LogCacheFields.LOG) Log log) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Log(@JsonProperty(LogCacheFields.PAYLOAD) String payload, @JsonProperty(LogCacheFields.TYPE) String type) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AppFeature.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AppFeature.java new file mode 100644 index 0000000000..1a535ac1a5 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AppFeature.java @@ -0,0 +1,12 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 app feature resource ({@code GET /v3/apps/{guid}/features}). App features belong to the + * application entity. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3AppFeature(@JsonProperty(V3Fields.NAME) String name, @JsonProperty(V3Fields.ENABLED) Boolean enabled) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java new file mode 100644 index 0000000000..381c7320bf --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java @@ -0,0 +1,50 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 application resource ({@code GET/POST /v3/apps}). + * + *
+ * { "guid": "...", "name": "...", "state": "STARTED|STOPPED", "created_at": "...", "updated_at": "...",
+ *   "lifecycle": { "type": "buildpack|docker|cnb", "data": { "buildpacks": [...], "stack": "..." } },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "space": { "data": { "guid": "..." } } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Application(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty("lifecycle") V3Lifecycle lifecycle, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Lifecycle(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.DATA) V3LifecycleData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3LifecycleData(@JsonProperty(V3Fields.BUILDPACKS) List buildpacks, @JsonProperty(V3Fields.STACK) String stack) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3EnvironmentVariables(@JsonProperty("var") Map environmentVariables) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java new file mode 100644 index 0000000000..be823aa926 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java @@ -0,0 +1,55 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableLifecycle; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle; +import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; + +public final class V3ApplicationMapper { + + private V3ApplicationMapper() { + } + + public static CloudApplication toCloudApplication(V3Application app, CloudSpace space) { + return ImmutableCloudApplication.builder() + .metadata(V3ResourceMappers.parseMetadata(app.guid(), app.createdAt(), app.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(app.metadata())) + .name(app.name()) + .state(parseState(app.state())) + .lifecycle(parseLifecycle(app.lifecycle())) + .space(space) + .build(); + } + + private static CloudApplication.State parseState(String state) { + return state == null ? null : CloudApplication.State.valueOf(state); + } + + private static Lifecycle parseLifecycle(V3Application.V3Lifecycle lifecycle) { + if (lifecycle == null || lifecycle.type() == null) { + return null; + } + + return ImmutableLifecycle.builder() + .type(LifecycleType.valueOf(lifecycle.type() + .toUpperCase())) + .data(extractLifecycleData(lifecycle.data())) + .build(); + } + + private static Map extractLifecycleData(V3Application.V3LifecycleData data) { + if (data == null) { + return Map.of(); + } + + return Map.of(V3Fields.BUILDPACKS, data.buildpacks() == null ? List.of() : data.buildpacks(), V3Fields.STACK, + data.stack() == null ? Constants.EMPTY_STRING : data.stack()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java new file mode 100644 index 0000000000..85e330b0ab --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java @@ -0,0 +1,26 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 audit event resource ({@code GET /v3/audit_events}). + * + *
+ * { "guid": "...", "created_at": "...", "updated_at": "...", "type": "audit.app.update",
+ *   "actor":  { "guid": "...", "type": "user",        "name": "..." },
+ *   "target": { "guid": "...", "type": "app",         "name": "..." },
+ *   "data": { ... }, "space": {...}, "organization": {...} }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3AuditEvent(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.ACTOR) V3Participant actor, @JsonProperty(V3Fields.TARGET) V3Participant target) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Participant(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.NAME) String name) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java new file mode 100644 index 0000000000..e06b1b1518 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java @@ -0,0 +1,35 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent.Participant; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent.ImmutableParticipant; + +public final class V3AuditEventMapper { + + private V3AuditEventMapper() { + } + + public static CloudEvent toCloudEvent(V3AuditEvent event) { + return ImmutableCloudEvent.builder() + .metadata(V3ResourceMappers.parseMetadata(event.guid(), event.createdAt(), event.updatedAt())) + .target(parseParticipant(event.target())) + .actor(parseParticipant(event.actor())) + .type(event.type()) + .build(); + } + + private static Participant parseParticipant(V3AuditEvent.V3Participant participant) { + if (participant == null) { + return ImmutableParticipant.builder() + .build(); + } + + return ImmutableParticipant.builder() + .guid(V3ResourceMappers.parseNullableGuid(participant.guid())) + .name(participant.name()) + .type(participant.type()) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java new file mode 100644 index 0000000000..5866cfa952 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java @@ -0,0 +1,51 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 build resource ({@code GET/POST /v3/builds}, {@code GET /v3/apps/{guid}/builds}). + * + *
+ * { "guid": "...", "created_at": "...", "updated_at": "...",
+ *   "state": "STAGING|STAGED|FAILED", "error": "...",
+ *   "created_by": { "guid": "...", "name": "..." },
+ *   "package": { "guid": "..." },
+ *   "droplet": { "guid": "..." },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "app": { "data": { "guid": "..." } } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Build(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.ERROR) String error, @JsonProperty(V3Fields.CREATED_BY) V3CreatedBy createdBy, + @JsonProperty(V3Fields.PACKAGE) V3PackageReference inputPackage, + @JsonProperty(V3Fields.DROPLET) V3DropletReference droplet, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3CreatedBy(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3PackageReference(@JsonProperty(V3Fields.GUID) String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3DropletReference(@JsonProperty(V3Fields.GUID) String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship app) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java new file mode 100644 index 0000000000..fffddea5e7 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java @@ -0,0 +1,59 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild.ImmutableCreatedBy; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild.ImmutablePackageInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; + +public final class V3BuildMapper { + + private V3BuildMapper() { + } + + public static CloudBuild toCloudBuild(V3Build build) { + return ImmutableCloudBuild.builder() + .metadata(V3ResourceMappers.parseMetadata(build.guid(), build.createdAt(), build.updatedAt())) + .createdBy(parseCreatedBy(build.createdBy())) + .packageInfo(parsePackageInfo(build.inputPackage())) + .dropletInfo(parseDropletInfo(build.droplet())) + .state(parseState(build.state())) + .error(build.error()) + .build(); + } + + private static CloudBuild.CreatedBy parseCreatedBy(V3Build.V3CreatedBy createdBy) { + if (createdBy == null) { + return null; + } + + return ImmutableCreatedBy.builder() + .guid(V3ResourceMappers.parseNullableGuid(createdBy.guid())) + .name(createdBy.name()) + .build(); + } + + private static CloudBuild.PackageInfo parsePackageInfo(V3Build.V3PackageReference inputPackage) { + if (inputPackage == null) { + return null; + } + + return ImmutablePackageInfo.of(V3ResourceMappers.parseNullableGuid(inputPackage.guid())); + } + + private static DropletInfo parseDropletInfo(V3Build.V3DropletReference droplet) { + if (droplet == null || droplet.guid() == null) { + return null; + } + + return ImmutableDropletInfo.builder() + .guid(V3ResourceMappers.parseNullableGuid(droplet.guid())) + .build(); + } + + private static CloudBuild.State parseState(String state) { + return state == null ? null : CloudBuild.State.valueOf(state); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java new file mode 100644 index 0000000000..460627b04f --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java @@ -0,0 +1,40 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 domain resource ({@code GET/POST /v3/domains}, {@code GET /v3/organizations/{guid}/domains}, + * {@code GET /v3/organizations/{guid}/domains/default}). + * + *
+ * { "guid": "...", "name": "example.com", "internal": false, "created_at": "...", "updated_at": "...",
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "organization": { "data": { "guid": "..." } | null } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Domain(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.INTERNAL) Boolean internal, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3DomainRelationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3DomainRelationships(@JsonProperty(V3Fields.ORGANIZATION) V3ToOneRelationship organization) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + + public boolean isPrivate() { + return relationships != null && relationships.organization() != null && relationships.organization() + .data() != null; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java new file mode 100644 index 0000000000..b0f9af0eed --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java @@ -0,0 +1,18 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; + +public final class V3DomainMapper { + + private V3DomainMapper() { + } + + public static CloudDomain toCloudDomain(V3Domain domain) { + return ImmutableCloudDomain.builder() + .metadata(V3ResourceMappers.parseMetadata(domain.guid(), domain.createdAt(), domain.updatedAt())) + .name(domain.name()) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java new file mode 100644 index 0000000000..561b2c7e1c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java @@ -0,0 +1,56 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 droplet resource ({@code GET /v3/droplets/{guid}}, + * {@code GET /v3/apps/{guid}/droplets/current}). + * + *
+ * { "guid": "...", "created_at": "...", "updated_at": "...",
+ *   "state": "AWAITING_UPLOAD|PROCESSING_UPLOAD|STAGED|COPYING|FAILED|EXPIRED", "error": "...",
+ *   "lifecycle": { "type": "buildpack|docker|cnb", "data": { "buildpacks": [...], "stack": "..." } },
+ *   "execution_metadata": "...", "process_types": { "web": "..." },
+ *   "relationships": { "app": { "data": { "guid": "..." } } },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "links": { "package": { "href": "https://.../v3/packages/{guid}" }, ... } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Droplet(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.ERROR) String error, @JsonProperty(V3Fields.LIFECYCLE) V3Lifecycle lifecycle, + @JsonProperty(V3Fields.EXECUTION_METADATA) String executionMetadata, + @JsonProperty(V3Fields.PROCESS_TYPES) Map processTypes, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.LINKS) Map links) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Lifecycle(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.DATA) V3LifecycleData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3LifecycleData(@JsonProperty(V3Fields.BUILDPACKS) List buildpacks, @JsonProperty(V3Fields.STACK) String stack) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship app) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Link(@JsonProperty(V3Fields.HREF) String href) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java new file mode 100644 index 0000000000..387290aac3 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java @@ -0,0 +1,117 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +public class V3Fields { + + public static final String ID = "id"; + public static final String GUID = "guid"; + public static final String NAME = "name"; + public static final String TYPE = "type"; + public static final String STATE = "state"; + public static final String DESCRIPTION = "description"; + public static final String CREATED_AT = "created_at"; + public static final String UPDATED_AT = "updated_at"; + public static final String METADATA = "metadata"; + public static final String DATA = "data"; + public static final String RELATIONSHIPS = "relationships"; + public static final String APP = "app"; + public static final String SPACE = "space"; + public static final String ORGANIZATION = "organization"; + public static final String SERVICE_INSTANCE = "service_instance"; + public static final String SERVICE_PLAN = "service_plan"; + public static final String DOMAIN = "domain"; + public static final String LABELS = "labels"; + public static final String ANNOTATIONS = "annotations"; + public static final String COMMAND = "command"; + public static final String INSTANCES = "instances"; + public static final String MEMORY_IN_MB = "memory_in_mb"; + public static final String DISK_IN_MB = "disk_in_mb"; + public static final String LIFECYCLE = "lifecycle"; + public static final String BUILDPACKS = "buildpacks"; + public static final String BUILDPACK = "buildpack"; + public static final String STACK = "stack"; + public static final String ENVIRONMENT_VARIABLES = "environment_variables"; + public static final String VAR = "var"; + public static final String HEALTH_CHECK = "health_check"; + public static final String READINESS_HEALTH_CHECK = "readiness_health_check"; + public static final String ENDPOINT = "endpoint"; + public static final String INTERVAL = "interval"; + public static final String INVOCATION_TIMEOUT = "invocation_timeout"; + public static final String TIMEOUT = "timeout"; + public static final String TAGS = "tags"; + public static final String PARAMETERS = "parameters"; + public static final String CREDENTIALS = "credentials"; + public static final String SYSLOG_DRAIN_URL = "syslog_drain_url"; + public static final String DOCKER = "docker"; + public static final String BITS = "bits"; + public static final String IMAGE = "image"; + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; + public static final String PACKAGE = "package"; + public static final String HOST = "host"; + public static final String PATH = "path"; + public static final String PORT = "port"; + public static final String PROTOCOL = "protocol"; + public static final String URL = "url"; + public static final String DESTINATIONS = "destinations"; + public static final String AUTHENTICATION = "authentication"; + public static final String DETAIL = "detail"; + public static final String ENABLED = "enabled"; + public static final String ACTOR = "actor"; + public static final String TARGET = "target"; + public static final String PUBLIC_VISIBILITY = "public"; + public static final String ERROR = "error"; + public static final String CREATED_BY = "created_by"; + public static final String DROPLET = "droplet"; + public static final String HREF = "href"; + public static final String LINKS = "links"; + public static final String OPERATION = "operation"; + public static final String WARNINGS = "warnings"; + public static final String ERRORS = "errors"; + public static final String CODE = "code"; + public static final String TITLE = "title"; + public static final String PAGINATION = "pagination"; + public static final String RESOURCES = "resources"; + public static final String TOTAL_PAGES = "total_pages"; + public static final String TOTAL_RESULTS = "total_results"; + public static final String NEXT = "next"; + public static final String PREVIOUS = "previous"; + public static final String CHECKSUM = "checksum"; + public static final String VALUE = "value"; + public static final String INDEX = "index"; + public static final String ROUTABLE = "routable"; + public static final String REASON = "reason"; + public static final String INTERNAL = "internal"; + public static final String EXECUTION_METADATA = "execution_metadata"; + public static final String PROCESS_TYPES = "process_types"; + public static final String USER = "user"; + public static final String VERSION = "version"; + public static final String LOG_RATE_LIMIT_IN_BYTES_PER_SECOND = "log_rate_limit_in_bytes_per_second"; + public static final String REVISION = "revision"; + public static final String WEIGHT = "weight"; + public static final String LAST_OPERATION = "last_operation"; + public static final String ROUTE_SERVICE_URL = "route_service_url"; + public static final String SERVICE_OFFERING = "service_offering"; + public static final String FREE = "free"; + public static final String VISIBILITY_TYPE = "visibility_type"; + public static final String BROKER_CATALOG = "broker_catalog"; + public static final String AVAILABLE = "available"; + public static final String AMOUNT = "amount"; + public static final String CURRENCY = "currency"; + public static final String UNIT = "unit"; + public static final String COSTS = "costs"; + public static final String MAINTENANCE_INFO = "maintenance_info"; + public static final String CREATE = "create"; + public static final String UPDATE = "update"; + public static final String SERVICE_BINDING = "service_binding"; + public static final String SCHEMAS = "schemas"; + public static final String QUOTA = "quota"; + public static final String SUSPENDED = "suspended"; + public static final String BASIC = "basic"; + public static final String MANAGED = "managed"; + public static final String USER_PROVIDED = "user-provided"; + public static final String KEY = "key"; + + private V3Fields() { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3GuidReference.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3GuidReference.java new file mode 100644 index 0000000000..e039088e9d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3GuidReference.java @@ -0,0 +1,8 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3GuidReference(@JsonProperty(V3Fields.GUID) String guid) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java new file mode 100644 index 0000000000..a5931a7eaa --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java @@ -0,0 +1,41 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstanceInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; + +public final class V3InstancesInfoMapper { + + private V3InstancesInfoMapper() { + } + + public static InstancesInfo toInstancesInfo(V3Process.V3ProcessStats processStats) { + return ImmutableInstancesInfo.builder() + .instances(parseProcessStatistics(processStats.resources())) + .build(); + } + + private static List parseProcessStatistics(List stats) { + if (stats == null) { + return Collections.emptyList(); + } + + return stats.stream() + .map(V3InstancesInfoMapper::parseProcessStatistic) + .toList(); + } + + private static InstanceInfo parseProcessStatistic(V3Process.V3ProcessStatsResource statsResource) { + return ImmutableInstanceInfo.builder() + .index(statsResource.index() == null ? 0 : statsResource.index()) + .state(InstanceState.valueOfWithDefault(statsResource.state())) + .isRoutable(statsResource.routable() != null && Boolean.parseBoolean(statsResource.routable())) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java new file mode 100644 index 0000000000..e963883102 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java @@ -0,0 +1,44 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.multiapps.controller.Constants; + +/** + * Thin Jackson wire-model of a Cloud Controller v3 job resource ({@code GET /v3/jobs/{guid}}). + * + *
+ * { "guid": "...", "created_at": "...", "updated_at": "...", "state": "PROCESSING|POLLING|COMPLETE|FAILED", "operation": "...",
+ *   "errors": [ { "detail": "...", "title": "...", "code": N } ], "warnings": [ { "detail": "..." } ] }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Job(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.OPERATION) String operation, @JsonProperty(V3Fields.ERRORS) List errors, + @JsonProperty(V3Fields.WARNINGS) List warnings) { + + public boolean isComplete() { + return Constants.JOB_STATE_COMPLETE.equals(state); + } + + public boolean isFailed() { + return Constants.JOB_STATE_FAILED.equals(state); + } + + public boolean isTerminal() { + return isComplete() || isFailed(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Error(@JsonProperty(V3Fields.DETAIL) String detail, @JsonProperty(V3Fields.TITLE) String title, + @JsonProperty(V3Fields.CODE) Integer code) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Warning(@JsonProperty(V3Fields.DETAIL) String detail) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java new file mode 100644 index 0000000000..551b1f6589 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java @@ -0,0 +1,56 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; + +public final class V3JobMapper { + + private V3JobMapper() { + } + + public static CloudAsyncJob toCloudAsyncJob(V3Job job) { + return ImmutableCloudAsyncJob.builder() + .metadata(V3ResourceMappers.parseMetadata(job.guid(), job.createdAt(), job.updatedAt())) + .state(parseState(job.state())) + .operation(job.operation()) + .warnings(getWarnings(job)) + .errors(getErrors(job)) + .build(); + } + + private static JobState parseState(String state) { + return JobState.valueOf(state.toUpperCase()); + } + + private static String getWarnings(V3Job job) { + List warnings = job.warnings(); + if (warnings == null) { + return Constants.EMPTY_STRING; + } + + return warnings.stream() + .map(V3Job.V3Warning::detail) + .collect(Collectors.joining(Constants.COMMA)); + } + + private static String getErrors(V3Job job) { + List errors = job.errors(); + if (errors == null) { + return Constants.EMPTY_STRING; + } + + return errors.stream() + .map(V3JobMapper::joinErrorDetails) + .collect(Collectors.joining(Constants.COMMA)); + } + + private static String joinErrorDetails(V3Job.V3Error error) { + return String.join(Constants.SPACE_SYMBOL, String.valueOf(error.code()), error.title(), error.detail()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java new file mode 100644 index 0000000000..5fb490078c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java @@ -0,0 +1,46 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a Cloud Controller v3 list response envelope: + * + *
+ * {
+ *   "pagination": { "total_results": N, "next": { "href": "..." } | null, ... },
+ *   "resources":  [ ... ]
+ * }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ListResponse(@JsonProperty(V3Fields.PAGINATION) Pagination pagination, + @JsonProperty(V3Fields.RESOURCES) List resources) { + + public List resources() { + return resources == null ? Collections.emptyList() : resources; + } + + public String nextPageHref() { + if (pagination == null || pagination.next() == null) { + return null; + } + + return pagination.next() + .href(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Pagination(@JsonProperty(V3Fields.TOTAL_RESULTS) Integer totalResults, + @JsonProperty(V3Fields.TOTAL_PAGES) Integer totalPages, + @JsonProperty(V3Fields.NEXT) Link next, @JsonProperty(V3Fields.PREVIOUS) Link previous) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Link(@JsonProperty(V3Fields.HREF) String href) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java new file mode 100644 index 0000000000..9dc0fb375a --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java @@ -0,0 +1,24 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of the CF v3 {@code metadata} object (labels + annotations) attached to most resources. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Metadata(@JsonProperty(V3Fields.LABELS) Map labels, + @JsonProperty(V3Fields.ANNOTATIONS) Map annotations) { + + public Map labels() { + return labels == null ? Collections.emptyMap() : labels; + } + + public Map annotations() { + return annotations == null ? Collections.emptyMap() : annotations; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java new file mode 100644 index 0000000000..b70e8b6124 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java @@ -0,0 +1,13 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 organization resource ({@code GET /v3/organizations/{guid}} / {@code GET /v3/organizations?...}). + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Organization(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java new file mode 100644 index 0000000000..44fa45865e --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java @@ -0,0 +1,51 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 package resource ({@code GET/POST /v3/packages}, {@code GET /v3/apps/{guid}/packages}). + * + *
+ * { "guid": "...", "type": "bits|docker", "state": "AWAITING_UPLOAD|PROCESSING_UPLOAD|READY|FAILED|COPYING|EXPIRED",
+ *   "created_at": "...", "updated_at": "...",
+ *   "data": {
+ *     // bits:
+ *     "checksum": { "type": "sha256", "value": "..." }, "error": "...",
+ *     // docker:
+ *     "image": "...", "username": "...", "password": "..."
+ *   },
+ *   "relationships": { "app": { "data": { "guid": "..." } } }
+ *   "metadata": { "labels": {...}, "annotations": {...} }}
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Package(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.DATA) V3PackageData data, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3PackageData(@JsonProperty(V3Fields.CHECKSUM) V3Checksum checksum, @JsonProperty(V3Fields.ERROR) String error, + @JsonProperty(V3Fields.IMAGE) String image, @JsonProperty(V3Fields.USERNAME) String username, + @JsonProperty(V3Fields.PASSWORD) String password) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Checksum(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.VALUE) String value) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship app) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java new file mode 100644 index 0000000000..98b8c24a9d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java @@ -0,0 +1,77 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableBitsData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; + +public final class V3PackageMapper { + + private V3PackageMapper() { + } + + public static CloudPackage toCloudPackage(V3Package resource) { + return ImmutableCloudPackage.builder() + .metadata(V3ResourceMappers.parseMetadata(resource.guid(), resource.createdAt(), + resource.updatedAt())) + .status(parseStatus(resource)) + .data(parseData(resource)) + .type(parseType(resource)) + .build(); + } + + private static Status parseStatus(V3Package resource) { + return resource.state() == null ? null : Status.valueOf(resource.state()); + } + + private static CloudPackage.Type parseType(V3Package resource) { + return resource.type() == null ? null : CloudPackage.Type.from(resource.type()); + } + + private static CloudPackage.PackageData parseData(V3Package resource) { + if (resource.type() != null && CloudPackage.Type.from(resource.type()) == CloudPackage.Type.BITS) { + return parseBitsData(resource.data()); + } + + return parseDockerData(resource.data()); + } + + private static CloudPackage.PackageData parseBitsData(V3Package.V3PackageData data) { + if (data == null) { + return ImmutableBitsData.builder() + .build(); + } + + return ImmutableBitsData.builder() + .checksum(parseBitsChecksum(data.checksum())) + .error(data.error()) + .build(); + } + + private static BitsData.Checksum + parseBitsChecksum(V3Package.V3Checksum checksum) { + if (checksum == null) { + return null; + } + + return ImmutableBitsData.ImmutableChecksum.builder() + .algorithm(checksum.type()) + .value(checksum.value()) + .build(); + } + + private static CloudPackage.PackageData parseDockerData(V3Package.V3PackageData data) { + if (data == null || data.image() == null) { + return null; + } + + return ImmutableDockerData.builder() + .image(data.image()) + .username(data.username()) + .password(data.password()) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java new file mode 100644 index 0000000000..def5e566d1 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java @@ -0,0 +1,72 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-models for the CF v3 process family of endpoints consumed by the client: + * + *
+ * // process
+ * { "guid": "...", "type": "web", "command": "...", "user": "vcap", "instances": 1,
+ *   "memory_in_mb": 256, "disk_in_mb": 1024, "log_rate_limit_in_bytes_per_second": 1024,
+ *   "health_check": { "type": "port|http|process", "data": { "timeout": N, "invocation_timeout": N, "endpoint": "...", "interval": N } },
+ *   "readiness_health_check": { "type": "process|http|port", "data": { "invocation_timeout": N, "endpoint": "...", "interval": N } },
+ *   "relationships": { "app": { "data": { "guid": "..." } }, "revision": { "data": { "guid": "..." } } },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "created_at": "...", "updated_at": "...", "version": "...",
+ *   "links": { "self": { "href": "..." }, "scale": { "href": "..." }, ... } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Process(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.COMMAND) String command, + @JsonProperty(V3Fields.INSTANCES) Integer instances, @JsonProperty(V3Fields.MEMORY_IN_MB) Integer memoryInMb, + @JsonProperty(V3Fields.DISK_IN_MB) Integer diskInMb, @JsonProperty(V3Fields.HEALTH_CHECK) V3HealthCheck healthCheck, + @JsonProperty(V3Fields.READINESS_HEALTH_CHECK) V3HealthCheck readinessHealthCheck, + @JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.USER) String user, + @JsonProperty(V3Fields.LOG_RATE_LIMIT_IN_BYTES_PER_SECOND) Integer logRateLimitInBytesPerSecond, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.VERSION) String version, + @JsonProperty(V3Fields.LINKS) Map links) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3HealthCheck(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.DATA) V3HealthCheckData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3HealthCheckData(@JsonProperty(V3Fields.TIMEOUT) Integer timeout, + @JsonProperty(V3Fields.INVOCATION_TIMEOUT) Integer invocationTimeout, + @JsonProperty(V3Fields.ENDPOINT) String endpoint, @JsonProperty(V3Fields.INTERVAL) Integer interval) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship app, + @JsonProperty(V3Fields.REVISION) V3ToOneRelationship revision) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Link(@JsonProperty(V3Fields.HREF) String href) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ProcessStats(@JsonProperty(V3Fields.RESOURCES) List resources) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ProcessStatsResource(@JsonProperty(V3Fields.INDEX) Integer index, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.ROUTABLE) String routable) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java new file mode 100644 index 0000000000..65864fee8c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java @@ -0,0 +1,59 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudProcess; + +public final class V3ProcessMapper { + + private V3ProcessMapper() { + } + + public static CloudProcess toCloudProcess(V3Process process) { + V3Process.V3HealthCheck healthCheck = process.healthCheck(); + V3Process.V3HealthCheck readinessHealthCheck = process.readinessHealthCheck(); + + Integer healthCheckTimeout = null; + String healthCheckHttpEndpoint = null; + Integer healthCheckInvocationTimeout = null; + Integer healthCheckInterval = null; + + if (healthCheck != null && healthCheck.data() != null) { + V3Process.V3HealthCheckData healthCheckData = healthCheck.data(); + healthCheckTimeout = healthCheckData.timeout(); + healthCheckInvocationTimeout = healthCheckData.invocationTimeout(); + healthCheckHttpEndpoint = healthCheckData.endpoint(); + healthCheckInterval = healthCheckData.interval(); + } + + Integer readinessHealthCheckInvocationTimeout = null; + String readinessHealthCheckHttpEndpoint = null; + Integer readinessHealthCheckInterval = null; + + if (readinessHealthCheck != null && readinessHealthCheck.data() != null) { + V3Process.V3HealthCheckData readinessHealthCheckData = readinessHealthCheck.data(); + readinessHealthCheckInvocationTimeout = readinessHealthCheckData.invocationTimeout(); + readinessHealthCheckHttpEndpoint = readinessHealthCheckData.endpoint(); + readinessHealthCheckInterval = readinessHealthCheckData.interval(); + } + + return ImmutableCloudProcess.builder() + .command(process.command()) + .instances(process.instances()) + .memoryInMb(process.memoryInMb()) + .diskInMb(process.diskInMb()) + .healthCheckType(healthCheck == null || healthCheck.type() == null ? HealthCheckType.PORT + : HealthCheckType.valueOf(healthCheck.type() + .toUpperCase())) + .healthCheckHttpEndpoint(healthCheckHttpEndpoint) + .healthCheckTimeout(healthCheckTimeout) + .healthCheckInvocationTimeout(healthCheckInvocationTimeout) + .healthCheckInterval(healthCheckInterval) + .readinessHealthCheckType(readinessHealthCheck == null ? null : readinessHealthCheck.type()) + .readinessHealthCheckHttpEndpoint(readinessHealthCheckHttpEndpoint) + .readinessHealthCheckInvocationTimeout(readinessHealthCheckInvocationTimeout) + .readinessHealthCheckInterval(readinessHealthCheckInterval) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3QueryParameters.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3QueryParameters.java new file mode 100644 index 0000000000..96ea2c073c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3QueryParameters.java @@ -0,0 +1,26 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +public class V3QueryParameters { + + private V3QueryParameters() { + } + + public static final String NAMES = "names"; + public static final String HOSTS = "hosts"; + public static final String PATHS = "paths"; + public static final String GUIDS = "guids"; + public static final String TYPE = "type"; + public static final String APP_GUIDS = "app_guids"; + public static final String SERVICE_INSTANCE_GUIDS = "service_instance_guids"; + public static final String SERVICE_BROKER_GUIDS = "service_broker_guids"; + public static final String SERVICE_OFFERING_GUIDS = "service_offering_guids"; + public static final String SERVICE_BROKER_NAMES = "service_broker_names"; + public static final String PACKAGE_GUIDS = "package_guids"; + public static final String ORGANIZATION_GUIDS = "organization_guids"; + public static final String TARGET_GUIDS = "target_guids"; + public static final String SPACE_GUIDS = "space_guids"; + public static final String DOMAIN_GUIDS = "domain_guids"; + public static final String USER_GUIDS = "user_guids"; + public static final String LABEL_SELECTOR = "label_selector"; + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java new file mode 100644 index 0000000000..ad60ae6273 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java @@ -0,0 +1,70 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.text.MessageFormat; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.format.DateTimeParseException; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class V3ResourceMappers { + + private static final Logger LOGGER = LoggerFactory.getLogger(V3ResourceMappers.class); + + private V3ResourceMappers() { + } + + public static CloudMetadata parseMetadata(String guid, String createdAt, String updatedAt) { + return ImmutableCloudMetadata.builder() + .guid(parseNullableGuid(guid)) + .createdAt(parseNullableDate(createdAt)) + .updatedAt(parseNullableDate(updatedAt)) + .build(); + } + + public static Metadata toV3Metadata(V3Metadata metadata) { + if (metadata == null) { + return null; + } + + return ImmutableMetadata.builder() + .labels(metadata.labels()) + .annotations(metadata.annotations()) + .build(); + } + + public static UUID parseNullableGuid(String guid) { + if (guid == null) { + return null; + } + + try { + return UUID.fromString(guid); + } catch (IllegalArgumentException e) { + LOGGER.warn(MessageFormat.format(Messages.COULD_NOT_PARSE_GUID_STRING, guid), e); + return null; + } + } + + public static LocalDateTime parseNullableDate(String date) { + if (date == null) { + return null; + } + + try { + return OffsetDateTime.parse(date) + .toLocalDateTime(); + } catch (DateTimeParseException e) { + LOGGER.warn(MessageFormat.format(Messages.COULD_NOT_PARSE_DATE_STRING, date), e); + return null; + } + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java new file mode 100644 index 0000000000..bdd6230dd6 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java @@ -0,0 +1,35 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 role resource ({@code GET /v3/roles}). + * + *
+ * { "guid": "...", "created_at": "...", "updated_at": "...",
+ *   "type": "organization_auditor|organization_billing_manager|organization_manager|organization_user|space_auditor|space_developer|space_manager",
+ *   "relationships": { "user": { "data": { "guid": "..." } }, "space": { "data": { "guid": "..." } | null },
+ *                      "organization": { "data": { "guid": "..." } | null } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Role(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.USER) V3ToOneRelationship user, + @JsonProperty(V3Fields.SPACE) V3ToOneRelationship space, + @JsonProperty(V3Fields.ORGANIZATION) V3ToOneRelationship organization) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java new file mode 100644 index 0000000000..7e5d4372a1 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java @@ -0,0 +1,14 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; + +public final class V3RoleMapper { + + private V3RoleMapper() { + } + + public static UserRole toUserRole(V3Role role) { + return UserRole.fromRoleType(role.type()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java new file mode 100644 index 0000000000..57fef8b9a4 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java @@ -0,0 +1,56 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 route resource ({@code GET/POST /v3/routes}, {@code GET /v3/apps/{guid}/routes}). + * + *
+ * { "guid": "...", "host": "...", "path": "...", "port": 8080, "url": "host.domain/path",
+ *   "created_at": "...", "updated_at": "...",
+ *   "destinations": [ { "guid": "...", "app": { "guid": "..." }, "port": 8080, "weight": 1, "protocol": "http1" } ],
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "space": { "data": { "guid": "..." } }, "domain": { "data": { "guid": "..." } } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Route(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.HOST) String host, + @JsonProperty(V3Fields.PATH) String path, + @JsonProperty(V3Fields.PORT) Integer port, @JsonProperty(V3Fields.URL) String url, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.DESTINATIONS) List destinations, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3RouteRelationships relationships) { + + public List destinations() { + return destinations == null ? Collections.emptyList() : destinations; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Destination(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.APP) V3DestinationApp app, + @JsonProperty(V3Fields.PORT) Integer port, @JsonProperty(V3Fields.WEIGHT) Integer weight, + @JsonProperty(V3Fields.PROTOCOL) String protocol) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3DestinationApp(@JsonProperty(V3Fields.GUID) String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RouteRelationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space, + @JsonProperty(V3Fields.DOMAIN) V3ToOneRelationship domain) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java new file mode 100644 index 0000000000..94b0877dad --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java @@ -0,0 +1,112 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableRouteDestination; +import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; +import org.springframework.util.StringUtils; + +public final class V3RouteMapper { + + private V3RouteMapper() { + } + + public static CloudRoute toCloudRoute(V3Route route) { + return toCloudRoute(route, null); + } + + public static CloudRoute toCloudRoute(V3Route route, UUID applicationGuid) { + List destinations = mapDestinations(route); + String domainGuid = extractDomainGuid(route); + + return ImmutableCloudRoute.builder() + .metadata(V3ResourceMappers.parseMetadata(route.guid(), route.createdAt(), route.updatedAt())) + .appsUsingRoute(route.destinations() + .size()) + .host(route.host()) + .port(route.port()) + .domain(ImmutableCloudDomain.builder() + .name(computeDomain(route)) + .metadata(ImmutableCloudMetadata.of( + V3ResourceMappers.parseNullableGuid(domainGuid))) + .build()) + .path(route.path()) + .url(route.url()) + .destinations(destinations) + .requestedProtocol(computeRequestedProtocol(destinations, applicationGuid)) + .build(); + } + + private static String extractDomainGuid(V3Route route) { + if (route.relationships() == null || route.relationships() + .domain() == null + || route.relationships() + .domain() + .data() == null) { + return null; + } + + return route.relationships() + .domain() + .data() + .guid(); + } + + private static String computeDomain(V3Route route) { + String domain = route.url(); + + if (StringUtils.hasText(route.host())) { + domain = domain.substring(route.host() + .length() + 1); + } + + if (StringUtils.hasText(route.path())) { + domain = domain.substring(0, domain.indexOf('/')); + } + + if (route.port() != null) { + domain = domain.substring(0, domain.indexOf(':')); + } + + return domain; + } + + private static List mapDestinations(V3Route route) { + return route.destinations() + .stream() + .filter(destination -> destination.app() != null && destination.app() + .guid() != null) + .map(destination -> ImmutableRouteDestination.builder() + .metadata(ImmutableCloudMetadata.builder() + .guid(V3ResourceMappers.parseNullableGuid( + destination.guid())) + .build()) + .applicationGuid(UUID.fromString(destination.app() + .guid())) + .port(destination.port()) + .weight(destination.weight()) + .protocol(destination.protocol()) + .build()) + .collect(Collectors.toList()); + } + + private static String computeRequestedProtocol(List destinations, UUID applicationGuid) { + if (applicationGuid == null) { + return null; + } + + return destinations.stream() + .filter(destination -> Objects.equals(destination.getApplicationGuid(), applicationGuid)) + .findFirst() + .map(RouteDestination::getProtocol) + .orElse(null); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java new file mode 100644 index 0000000000..cb25d419b9 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java @@ -0,0 +1,48 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service credential binding resource ({@code GET/POST/PATCH/DELETE /v3/service_credential_bindings}). A + * binding is either of {@code type=app} (an application bound to a service instance) or {@code type=key} (a service key); the + * {@code relationships.app} is absent for keys. + * + *
+ * { "guid": "...", "name": "...", "type": "app|key", "created_at": "...", "updated_at": "...",
+ *   "last_operation": { "type": "create|delete", "state": "initial|in progress|succeeded|failed",
+ *                       "description": "...", "created_at": "...", "updated_at": "..." },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "app": { "data": { "guid": "..." } | null },
+ *                      "service_instance": { "data": { "guid": "..." } } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceBinding(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.LAST_OPERATION) V3LastOperation lastOperation, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3LastOperation(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.DESCRIPTION) String description, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship application, + @JsonProperty(V3Fields.SERVICE_INSTANCE) V3ToOneRelationship serviceInstance) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java new file mode 100644 index 0000000000..a2a61b16a3 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java @@ -0,0 +1,65 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; + +public final class V3ServiceBindingMapper { + + private V3ServiceBindingMapper() { + } + + public static CloudServiceBinding toCloudServiceBinding(V3ServiceBinding binding) { + return ImmutableCloudServiceBinding.builder() + .metadata(V3ResourceMappers.parseMetadata(binding.guid(), binding.createdAt(), + binding.updatedAt())) + .applicationGuid(parseApplicationGuid(binding)) + .serviceInstanceGuid(parseServiceInstanceGuid(binding)) + .serviceBindingOperation(parseServiceBindingOperation(binding.lastOperation())) + .build(); + } + + private static UUID parseApplicationGuid(V3ServiceBinding binding) { + V3ServiceBinding.V3ToOneRelationship application = binding.relationships() == null ? null + : binding.relationships() + .application(); + + if (application == null || application.data() == null) { + return null; + } + + return V3ResourceMappers.parseNullableGuid(application.data() + .guid()); + } + + private static UUID parseServiceInstanceGuid(V3ServiceBinding binding) { + V3ServiceBinding.V3ToOneRelationship serviceInstance = binding.relationships() == null ? null + : binding.relationships() + .serviceInstance(); + + if (serviceInstance == null || serviceInstance.data() == null) { + return null; + } + + return V3ResourceMappers.parseNullableGuid(serviceInstance.data() + .guid()); + } + + private static ServiceCredentialBindingOperation parseServiceBindingOperation(V3ServiceBinding.V3LastOperation lastOperation) { + if (lastOperation == null) { + return null; + } + + return ImmutableServiceCredentialBindingOperation.builder() + .type(ServiceCredentialBindingOperation.Type.fromString(lastOperation.type())) + .state(ServiceCredentialBindingOperation.State.fromString(lastOperation.state())) + .description(lastOperation.description()) + .createdAt(V3ResourceMappers.parseNullableDate(lastOperation.createdAt())) + .updatedAt(V3ResourceMappers.parseNullableDate(lastOperation.updatedAt())) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java new file mode 100644 index 0000000000..5680a99484 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java @@ -0,0 +1,34 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service broker resource ({@code GET/POST/PATCH /v3/service_brokers}). + * + *
+ * { "guid": "...", "name": "...", "url": "...", "created_at": "...", "updated_at": "...",
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "space": { "data": { "guid": "..." } | null } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceBroker(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.URL) String url, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java new file mode 100644 index 0000000000..8b2b1cff2b --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java @@ -0,0 +1,31 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Optional; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; + +public final class V3ServiceBrokerMapper { + + private V3ServiceBrokerMapper() { + } + + public static CloudServiceBroker toCloudServiceBroker(V3ServiceBroker serviceBroker) { + return ImmutableCloudServiceBroker.builder() + .metadata(V3ResourceMappers.parseMetadata(serviceBroker.guid(), serviceBroker.createdAt(), + serviceBroker.updatedAt())) + .name(serviceBroker.name()) + .url(serviceBroker.url()) + .spaceGuid(getSpaceGuid(serviceBroker)) + .build(); + } + + private static String getSpaceGuid(V3ServiceBroker serviceBroker) { + return Optional.ofNullable(serviceBroker.relationships()) + .map(V3ServiceBroker.V3Relationships::space) + .map(V3ServiceBroker.V3ToOneRelationship::data) + .map(V3ServiceBroker.V3RelationshipData::guid) + .orElse(null); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java new file mode 100644 index 0000000000..640834f08c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java @@ -0,0 +1,49 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service instance resource ({@code GET/POST/PATCH /v3/service_instances}). + * + *
+ * { "guid": "...", "name": "...", "type": "managed|user-provided",
+ *   "created_at": "...", "updated_at": "...",
+ *   "tags": [...], "syslog_drain_url": "...", "route_service_url": "...",
+ *   "last_operation": { "type": "create|update|delete", "state": "succeeded|failed|in progress|initial", "description": "..." },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "space": { "data": { "guid": "..." } }, "service_plan": { "data": { "guid": "..." } } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceInstance(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.TAGS) List tags, + @JsonProperty(V3Fields.SYSLOG_DRAIN_URL) String syslogDrainUrl, + @JsonProperty(V3Fields.ROUTE_SERVICE_URL) String routeServiceUrl, + @JsonProperty(V3Fields.LAST_OPERATION) V3LastOperation lastOperation, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3LastOperation(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.DESCRIPTION) String description) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space, + @JsonProperty(V3Fields.SERVICE_PLAN) V3ToOneRelationship servicePlan) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java new file mode 100644 index 0000000000..5953c331d3 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java @@ -0,0 +1,63 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; + +public final class V3ServiceInstanceMapper { + + private V3ServiceInstanceMapper() { + } + + public static CloudServiceInstance toCloudServiceInstance(V3ServiceInstance serviceInstance, String planName, String labelName) { + return ImmutableCloudServiceInstance.builder() + .metadata(V3ResourceMappers.parseMetadata(serviceInstance.guid(), + serviceInstance.createdAt(), + serviceInstance.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(serviceInstance.metadata())) + .name(serviceInstance.name()) + .plan(planName) + .label(labelName) + .type(parseType(serviceInstance.type())) + .tags(serviceInstance.tags() == null ? java.util.Collections.emptyList() + : serviceInstance.tags()) + .lastOperation(parseLastOperation(serviceInstance.lastOperation())) + .syslogDrainUrl(serviceInstance.syslogDrainUrl()) + .build(); + } + + public static CloudServiceInstance toCloudServiceInstanceWithoutAuxiliaryContent(V3ServiceInstance serviceInstance) { + return ImmutableCloudServiceInstance.builder() + .metadata(V3ResourceMappers.parseMetadata(serviceInstance.guid(), + serviceInstance.createdAt(), + serviceInstance.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(serviceInstance.metadata())) + .name(serviceInstance.name()) + .tags(serviceInstance.tags() == null ? List.of() + : serviceInstance.tags()) + .build(); + } + + private static ServiceInstanceType parseType(String type) { + if (type == null) { + return null; + } + + return ServiceInstanceType.mapTypeToServiceInstanceType(type); + } + + private static ServiceOperation parseLastOperation(V3ServiceInstance.V3LastOperation lastOperation) { + if (lastOperation == null || lastOperation.type() == null || lastOperation.state() == null) { + return null; + } + + ServiceOperation.Type type = ServiceOperation.Type.fromString(lastOperation.type()); + ServiceOperation.State state = ServiceOperation.State.fromString(lastOperation.state()); + + return new ServiceOperation(type, lastOperation.description(), state); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java new file mode 100644 index 0000000000..2d1df8b410 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java @@ -0,0 +1,25 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of the CF v3 service credential binding details response + * ({@code GET /v3/service_credential_bindings/{guid}/details}), which is where the actual {@code credentials} of a service key live. + * + *
+ * { "credentials": { ... }, "syslog_drain_url": "...", "volume_mounts": [ ... ] }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceKeyDetails(@JsonProperty("credentials") Map credentials, + @JsonProperty("syslog_drain_url") String syslogDrainUrl) { + + public Map credentials() { + return credentials == null ? Collections.emptyMap() : credentials; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java new file mode 100644 index 0000000000..23a6f16816 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java @@ -0,0 +1,46 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; + +public final class V3ServiceKeyMapper { + + private V3ServiceKeyMapper() { + } + + public static CloudServiceKey toCloudServiceKey(V3ServiceBinding key, CloudServiceInstance serviceInstance) { + return toCloudServiceKey(key, serviceInstance, null); + } + + public static CloudServiceKey toCloudServiceKey(V3ServiceBinding key, CloudServiceInstance serviceInstance, + Map credentials) { + return ImmutableCloudServiceKey.builder() + .metadata(V3ResourceMappers.parseMetadata(key.guid(), key.createdAt(), key.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(key.metadata())) + .name(key.name()) + .credentials(credentials) + .serviceInstance(serviceInstance) + .serviceKeyOperation(parseServiceKeyOperation(key.lastOperation())) + .build(); + } + + private static ServiceCredentialBindingOperation parseServiceKeyOperation(V3ServiceBinding.V3LastOperation lastOperation) { + if (lastOperation == null) { + return null; + } + + return ImmutableServiceCredentialBindingOperation.builder() + .type(ServiceCredentialBindingOperation.Type.fromString(lastOperation.type())) + .state(ServiceCredentialBindingOperation.State.fromString(lastOperation.state())) + .description(lastOperation.description()) + .createdAt(V3ResourceMappers.parseNullableDate(lastOperation.createdAt())) + .updatedAt(V3ResourceMappers.parseNullableDate(lastOperation.updatedAt())) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java new file mode 100644 index 0000000000..94f61d0358 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java @@ -0,0 +1,55 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service offering resource ({@code GET /v3/service_offerings}). + * + *
+ * { "guid": "...", "name": "...", "description": "...", "available": true, "shareable": true,
+ *   "tags": [...], "documentation_url": "...", "created_at": "...", "updated_at": "...",
+ *   "broker_catalog": { "id": "...", "metadata": {...}, "features": { "bindable": true, ... } },
+ *   "relationships": { "service_broker": { "data": { "guid": "..." } } },
+ *   "metadata": { "labels": {...}, "annotations": {...} } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceOffering(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.DESCRIPTION) String description, @JsonProperty("available") Boolean available, + @JsonProperty("shareable") Boolean shareable, + @JsonProperty("documentation_url") String documentationUrl, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty("broker_catalog") V3BrokerCatalog brokerCatalog, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.TAGS) List tags) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3BrokerCatalog(@JsonProperty("id") String id, @JsonProperty(V3Fields.METADATA) Map metadata, + @JsonProperty("features") V3Features features) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Features(@JsonProperty("bindable") Boolean bindable, + @JsonProperty("allow_context_updates") Boolean allowContextUpdates, + @JsonProperty("bindings_retrievable") Boolean bindingsRetrievable, + @JsonProperty("instances_retrievable") Boolean instancesRetrievable, + @JsonProperty("plan_updatable") Boolean planUpdatable) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty("service_broker") V3ToOneRelationship serviceBroker) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java new file mode 100644 index 0000000000..d98d5f2b3f --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java @@ -0,0 +1,61 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceOffering; + +public final class V3ServiceOfferingMapper { + + private V3ServiceOfferingMapper() { + } + + public static CloudServiceOffering toCloudServiceOffering(V3ServiceOffering serviceOffering, List servicePlans) { + return ImmutableCloudServiceOffering.builder() + .metadata(V3ResourceMappers.parseMetadata(serviceOffering.guid(), + serviceOffering.createdAt(), + serviceOffering.updatedAt())) + .name(serviceOffering.name()) + .isAvailable(serviceOffering.available()) + .isBindable(extractBindable(serviceOffering.brokerCatalog())) + .description(serviceOffering.description()) + .isShareable(serviceOffering.shareable()) + .extra(extractBrokerCatalogMetadata(serviceOffering.brokerCatalog())) + .docUrl(serviceOffering.documentationUrl()) + .brokerId(extractBrokerId(serviceOffering.relationships())) + .uniqueId(extractBrokerCatalogId(serviceOffering.brokerCatalog())) + .servicePlans(servicePlans) + .build(); + } + + private static Boolean extractBindable(V3ServiceOffering.V3BrokerCatalog brokerCatalog) { + if (brokerCatalog == null || brokerCatalog.features() == null) { + return null; + } + + return brokerCatalog.features() + .bindable(); + } + + private static Map extractBrokerCatalogMetadata(V3ServiceOffering.V3BrokerCatalog brokerCatalog) { + return brokerCatalog == null ? null : brokerCatalog.metadata(); + } + + private static String extractBrokerCatalogId(V3ServiceOffering.V3BrokerCatalog brokerCatalog) { + return brokerCatalog == null ? null : brokerCatalog.id(); + } + + private static String extractBrokerId(V3ServiceOffering.V3Relationships relationships) { + if (relationships == null || relationships.serviceBroker() == null || relationships.serviceBroker() + .data() == null) { + return null; + } + + return relationships.serviceBroker() + .data() + .guid(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java new file mode 100644 index 0000000000..959c7c91eb --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java @@ -0,0 +1,117 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service plan resource ({@code GET /v3/service_plans}). + * + *
+ * { "guid": "...", "name": "...", "description": "...", "free": true|false,
+ *   "visibility_type": "public|admin|organization|space",
+ *   "available": true|false,
+ *   "costs": [
+ *     {
+ *       "currency": "USD",
+ *       "amount": 199.99,
+ *       "unit": "..."
+ *     },
+ *   "maintenance_info": {
+ *     "version": "1.0.0+dev4",
+ *     "description": "Database version 7.8.0"
+ *   },
+ *   "schemas": {
+ *     "service_instance": {
+ *       "create": {
+ *         "parameters": {
+ *           "$schema": "...",
+ *           "type": "object",
+ *           "properties": {
+ *             "billing-account": {
+ *               "description": "Billing account number used to charge use of shared fake server.",
+ *               "type": "string"
+ *             }
+ *           }
+ *         }
+ *       },
+ *       "update": {
+ *         "parameters": {}
+ *       }
+ *     },
+ *     "service_binding": {
+ *       "create": {
+ *         "parameters": {}
+ *       }
+ *     }
+ *   }
+ *   "created_at": "...", "updated_at": "...",
+ *   "broker_catalog": { "id": "...", "metadata": { ... } },
+ *   "relationships": { "service_offering": { "data": { "guid": "..." } } },
+ *   "metadata": { "labels": {...}, "annotations": {...} }
+ *   "links": { "self": { "href": "..." }, "scale": { "href": "..." }, ... }}
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServicePlan(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.DESCRIPTION) String description, @JsonProperty(V3Fields.FREE) Boolean free, + @JsonProperty(V3Fields.VISIBILITY_TYPE) String visibilityType, + @JsonProperty(V3Fields.AVAILABLE) Boolean available, + @JsonProperty(V3Fields.COSTS) List costs, + @JsonProperty(V3Fields.MAINTENANCE_INFO) V3ServicePlanMaintenanceInfo maintenanceInfo, + @JsonProperty(V3Fields.SCHEMAS) V3ServicePlanSchemas schemas, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.BROKER_CATALOG) V3BrokerCatalog brokerCatalog, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.LINKS) Map links) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3BrokerCatalog(@JsonProperty(V3Fields.ID) String id, @JsonProperty(V3Fields.METADATA) Map metadata) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.SERVICE_OFFERING) V3ToOneRelationship serviceOffering, + @JsonProperty(V3Fields.SPACE) V3ToOneRelationship space) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ServicePlanCost(@JsonProperty(V3Fields.AMOUNT) float amount, @JsonProperty(V3Fields.CURRENCY) String currency, + @JsonProperty(V3Fields.UNIT) String unit) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ServicePlanMaintenanceInfo(@JsonProperty(V3Fields.VERSION) String version, + @JsonProperty(V3Fields.DESCRIPTION) String description) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ServicePlanSchemas(@JsonProperty(V3Fields.SERVICE_INSTANCE) V3SchemaParameters serviceInstance, + @JsonProperty(V3Fields.SERVICE_BINDING) V3SchemaParameters serviceBinding) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3SchemaParameters(@JsonProperty(V3Fields.CREATE) V3SchemaParametersDefinition create, + @JsonProperty(V3Fields.UPDATE) V3SchemaParametersDefinition update) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3SchemaParametersDefinition(@JsonProperty(V3Fields.PARAMETERS) Map parameters) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Link(@JsonProperty(V3Fields.HREF) String href) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java new file mode 100644 index 0000000000..763159fb05 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java @@ -0,0 +1,51 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; + +public final class V3ServicePlanMapper { + + private V3ServicePlanMapper() { + } + + public static CloudServicePlan toCloudServicePlan(V3ServicePlan servicePlan) { + return ImmutableCloudServicePlan.builder() + .metadata(V3ResourceMappers.parseMetadata(servicePlan.guid(), servicePlan.createdAt(), + servicePlan.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(servicePlan.metadata())) + .name(servicePlan.name()) + .description(servicePlan.description()) + .extra(extractBrokerCatalogMetadata(servicePlan.brokerCatalog())) + .uniqueId(extractBrokerCatalogId(servicePlan.brokerCatalog())) + .serviceOfferingId(extractServiceOfferingId(servicePlan.relationships())) + .isPublic(isPublic(servicePlan.visibilityType())) + .isFree(servicePlan.free()) + .build(); + } + + private static Map extractBrokerCatalogMetadata(V3ServicePlan.V3BrokerCatalog brokerCatalog) { + return brokerCatalog == null ? null : brokerCatalog.metadata(); + } + + private static String extractBrokerCatalogId(V3ServicePlan.V3BrokerCatalog brokerCatalog) { + return brokerCatalog == null ? null : brokerCatalog.id(); + } + + private static String extractServiceOfferingId(V3ServicePlan.V3Relationships relationships) { + if (relationships == null || relationships.serviceOffering() == null || relationships.serviceOffering() + .data() == null) { + return null; + } + + return relationships.serviceOffering() + .data() + .guid(); + } + + private static Boolean isPublic(String visibilityType) { + return visibilityType == null ? null : V3Fields.PUBLIC_VISIBILITY.equals(visibilityType); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java new file mode 100644 index 0000000000..7b475ffddc --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java @@ -0,0 +1,57 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 space resource ({@code GET /v3/spaces/{guid}} / {@code GET /v3/spaces?...}). + * + *
+ * { "guid": "...", "name": "...", "created_at": "...", "updated_at": "...",
+ *   "relationships": { "organization": { "data": { "guid": "..." } }, "quota": { "data": { "guid": "..." } } },
+ *   "suspended": false,
+ *   "metadata": { "labels": {...}, "annotations": {...} }
+ *   "links": { "self": { "href": "..." }, "scale": { "href": "..." }, ... },
+ *   }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Space(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.SUSPENDED) Boolean suspended, + @JsonProperty(V3Fields.RELATIONSHIPS) V3SpaceRelationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.LINKS) Map links) { + + public String organizationGuid() { + if (relationships == null || relationships.organization() == null || relationships.organization() + .data() == null) { + return null; + } + + return relationships.organization() + .data() + .guid(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3SpaceRelationships(@JsonProperty(V3Fields.ORGANIZATION) V3ToOne organization, + @JsonProperty(V3Fields.QUOTA) V3ToOne quota) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOne(@JsonProperty(V3Fields.DATA) V3Data data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Data(@JsonProperty(V3Fields.GUID) String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Link(@JsonProperty(V3Fields.HREF) String href) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3SshEnabled.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3SshEnabled.java new file mode 100644 index 0000000000..0a4d2b8103 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3SshEnabled.java @@ -0,0 +1,12 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of the CF v3 SSH-enabled resource ({@code GET /v3/apps/{guid}/ssh_enabled}). This belongs to the + * application entity. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3SshEnabled(@JsonProperty(V3Fields.ENABLED) Boolean enabled, @JsonProperty(V3Fields.REASON) String reason) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java new file mode 100644 index 0000000000..815b670d9c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java @@ -0,0 +1,22 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 stack resource ({@code GET /v3/stacks}). + * + *
+ * { "guid": "...", "name": "cflinuxfs4", "description": "...",
+ *   "created_at": "...", "updated_at": "...",
+ *   "state": "ACTIVE",
+ *   "metadata": { "labels": {...}, "annotations": {...} } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Stack(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.DESCRIPTION) String description, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java new file mode 100644 index 0000000000..c821ec9104 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java @@ -0,0 +1,19 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudStack; + +public final class V3StackMapper { + + private V3StackMapper() { + } + + public static CloudStack toCloudStack(V3Stack stack) { + return ImmutableCloudStack.builder() + .metadata(V3ResourceMappers.parseMetadata(stack.guid(), stack.createdAt(), stack.updatedAt())) + .name(stack.name()) + .description(stack.description()) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java new file mode 100644 index 0000000000..7ed98abff8 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java @@ -0,0 +1,49 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 task resource ({@code GET/POST /v3/tasks}, {@code POST /v3/apps/{guid}/tasks}). + * + *
+ * { "guid": "...", "name": "...", "command": "...", "state": "PENDING|RUNNING|SUCCEEDED|CANCELING|FAILED",
+ *   "memory_in_mb": 256, "disk_in_mb": 1024,
+ *   "result": { "failure_reason": "..." | null },
+ *   "created_at": "...", "updated_at": "...",
+ *   "relationships": {
+ *     "app": {
+ *       "data": {
+ *         "guid": "ccc25a0f-c8f4-4b39-9f1b-de9f328d0ee5"
+ *       }
+ *     }
+ *   },
+ *   "metadata": { "labels": {...}, "annotations": {...} } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Task(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.COMMAND) String command, + @JsonProperty(V3Fields.STATE) String state, @JsonProperty("memory_in_mb") Integer memoryInMb, + @JsonProperty("disk_in_mb") Integer diskInMb, @JsonProperty("result") V3Result result, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.RELATIONSHIPS) V3TaskRelationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Result(@JsonProperty("failure_reason") String failureReason) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3TaskRelationships(@JsonProperty(V3Fields.APP) V3Task.V3ToOne app) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOne(@JsonProperty(V3Fields.DATA) V3Task.V3Data data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Data(@JsonProperty(V3Fields.GUID) String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java new file mode 100644 index 0000000000..bde50c807e --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java @@ -0,0 +1,42 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; + +public final class V3TaskMapper { + + private V3TaskMapper() { + } + + public static CloudTask toCloudTask(V3Task task) { + return ImmutableCloudTask.builder() + .metadata(V3ResourceMappers.parseMetadata(task.guid(), task.createdAt(), task.updatedAt())) + .name(task.name()) + .command(task.command()) + .limits(parseLimits(task)) + .result(parseResult(task)) + .state(parseState(task.state())) + .build(); + } + + private static CloudTask.Result parseResult(V3Task task) { + if (task.result() == null) { + return null; + } + + return ImmutableCloudTask.ImmutableResult.of(task.result() + .failureReason()); + } + + private static CloudTask.Limits parseLimits(V3Task task) { + return ImmutableCloudTask.ImmutableLimits.builder() + .disk(task.diskInMb()) + .memory(task.memoryInMb()) + .build(); + } + + private static CloudTask.State parseState(String state) { + return state == null ? null : CloudTask.State.valueOf(state); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/JobV3Util.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/JobV3Util.java deleted file mode 100644 index e7e293c426..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/JobV3Util.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.util; - -import java.time.Duration; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; - -import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v3.ClientV3Exception; -import org.cloudfoundry.client.v3.Error; -import org.cloudfoundry.client.v3.jobs.GetJobRequest; -import org.cloudfoundry.client.v3.jobs.GetJobResponse; -import org.cloudfoundry.client.v3.jobs.Job; -import org.cloudfoundry.client.v3.jobs.JobState; -import org.cloudfoundry.util.DelayUtils; - -import reactor.core.publisher.Mono; - -public class JobV3Util { - - private JobV3Util() { - } - - private static final Set FINAL_STATES = EnumSet.of(JobState.COMPLETE, JobState.FAILED); - - public static Mono waitForCompletion(CloudFoundryClient cloudFoundryClient, Duration completionTimeout, String jobId) { - return requestJobV3(cloudFoundryClient, jobId).filter(job -> FINAL_STATES.contains(job.getState())) - .repeatWhenEmpty(DelayUtils.exponentialBackOff(Duration.ofSeconds(1), - Duration.ofSeconds(15), - completionTimeout)) - .filter(job -> JobState.FAILED == job.getState()) - .flatMap(JobV3Util::getError); - } - - private static Mono requestJobV3(CloudFoundryClient cloudFoundryClient, String jobId) { - return cloudFoundryClient.jobsV3() - .get(GetJobRequest.builder() - .jobId(jobId) - .build()); - } - - private static Mono getError(Job job) { - List errors = job.getErrors(); - // Status code must be set, otherwise it will throw NPE during getStatusCode() invocation - return Mono.error(new ClientV3Exception(200, errors)); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/RestUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/RestUtil.java index 8b22d95fb3..161d966a28 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/RestUtil.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/RestUtil.java @@ -83,7 +83,7 @@ private SslContext buildSslContext() { } } - private X509ExtendedTrustManager createDummyTrustManager() { + public static X509ExtendedTrustManager createDummyTrustManager() { return new X509ExtendedTrustManager() { @Override diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/UriUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/UriUtil.java index fb1d4a9647..be8f03d3be 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/UriUtil.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/UriUtil.java @@ -1,9 +1,18 @@ package org.cloudfoundry.multiapps.controller.client.facade.util; -import org.springframework.web.util.UriUtils; - +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; import java.nio.charset.Charset; +import java.text.MessageFormat; import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.common.util.MiscUtil; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudException; +import org.springframework.web.util.UriUtils; public class UriUtil { @@ -12,9 +21,71 @@ private UriUtil() { } public static String encodeChars(String queryParam, List charsToEncode) { - for (String charToEncode: charsToEncode) { + for (String charToEncode : charsToEncode) { queryParam = queryParam.replaceAll(charToEncode, UriUtils.encode(charToEncode, Charset.defaultCharset())); } return queryParam; } + + public static URL extractBaseUrl(URL url) { + try { + int port = url.getPort(); + String origin = url.getProtocol() + Constants.PROTOCOL_SEPARATOR + url.getHost(); + + if (port != Constants.UNDEFINED_PORT) { + origin = origin + Constants.COLON + port; + } + + return URI.create(origin) + .toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(MessageFormat.format(Messages.COULD_NOT_DETERMINE_API_ORIGIN_FROM_0, url), e); + } + } + + private static URL logCacheFallbackUrl(URL controllerUrl) { + String host = controllerUrl.getHost(); + + String logCacheHost = host.startsWith(Constants.API_HOST_PREFIX) ? Constants.LOG_CACHE_PREFIX + host.substring( + Constants.API_HOST_PREFIX.length()) : Constants.LOG_CACHE_PREFIX + host; + try { + return URI.create(controllerUrl.getProtocol() + Constants.PROTOCOL_SEPARATOR + logCacheHost) + .toURL(); + } catch (MalformedURLException me) { + throw new CloudException(MessageFormat.format(Messages.COULD_NOT_RESOLVE_LOG_CACHE_URL_FROM_0, controllerUrl), me); + } + } + + private static URL apiFallbackUrl(URL controllerUrl, String pathSuffix) { + try { + return URI.create(controllerUrl.toString() + pathSuffix) + .toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException( + MessageFormat.format(Messages.COULD_NOT_RESOLVE_URL_FROM_0_1, controllerUrl, pathSuffix), e); + } + } + + public static URL resolveV3ApiUrl(URL controllerUrl, Map rootDocumentLinks) { + try { + Map cloudControllerUrlV3 = MiscUtil.cast(rootDocumentLinks.get( + Constants.CLOUD_CONTROLLER_CF_ROOT_DOCUMENT_NAME)); + return URI.create((String) cloudControllerUrlV3.get(Constants.HREF)) + .toURL(); + } catch (Exception e) { + return apiFallbackUrl(controllerUrl, Constants.CF_API_V3); + } + } + + public static URL resolveLogCacheUrl(URL controllerUrl, Map rootDocumentLinks) { + try { + Map logCacheUrl = MiscUtil.cast(rootDocumentLinks.get(Constants.LOG_CACHE_CF_ROOT_DOCUMENT_NAME)); + + return URI.create((String) logCacheUrl.get(Constants.HREF)) + .toURL(); + } catch (Exception e) { + return logCacheFallbackUrl(controllerUrl); + } + } + } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClientTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClientTest.java index d2deb2c952..c8285710c2 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClientTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClientTest.java @@ -199,18 +199,6 @@ void testBindServiceInstanceReturnsOptional() { Assertions.assertEquals(Optional.of("job-1"), client.bindServiceInstance("binding", "app", "service")); } - @Test - void testGetApplicationSshEnabledRetriesAndReturnsBoolean() { - UUID guid = UUID.randomUUID(); - Mockito.when(restClient.getApplicationSshEnabled(guid)) - .thenThrow(new CloudOperationException(HttpStatus.BAD_GATEWAY)) - .thenReturn(true); - - Assertions.assertTrue(client.getApplicationSshEnabled(guid)); - Mockito.verify(restClient, Mockito.times(2)) - .getApplicationSshEnabled(guid); - } - @Test void testAsyncUploadWithOverrideTimeoutUsesPlainRetry() { Path file = Paths.get("/tmp/some.mtar"); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java index 4cc72cbed1..6216ab339d 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java @@ -1,7 +1,14 @@ package org.cloudfoundry.multiapps.controller.client.facade; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.HealthCheckType; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; @@ -9,13 +16,16 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; @@ -26,15 +36,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.APPLICATION_HOST; import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.DEFAULT_DOMAIN; import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.DISK_IN_MB; @@ -85,7 +86,7 @@ void createApplication() { String applicationName = "test-app-1"; Staging staging = ImmutableStaging.builder() .addBuildpack(JAVA_BUILDPACK) - .healthCheckType(HealthCheckType.PROCESS.getValue()) + .healthCheckType(HealthCheckType.PROCESS.toString()) .healthCheckHttpEndpoint(HEALTH_CHECK_ENDPOINT) .healthCheckTimeout(HEALTH_CHECK_TIMEMOUT) .build(); @@ -157,27 +158,12 @@ void updateApplicationHealthcheckType() { String applicationName = "test-application-16"; try { createAndVerifyDefaultApplication(applicationName); + client.updateApplicationStaging(applicationName, ImmutableStaging.builder() + .healthCheckType(HealthCheckType.PROCESS.toString()) + .build()); UUID applicationGuid = client.getApplicationGuid(applicationName); - var processResponse = delegate.applicationsV3() - .getProcess(org.cloudfoundry.client.v3.applications.GetApplicationProcessRequest.builder() - .applicationId( - applicationGuid.toString()) - .type("web") - .build()) - .block(); - delegate.processes() - .update(org.cloudfoundry.client.v3.processes.UpdateProcessRequest.builder() - .processId(processResponse.getId()) - .healthCheck( - org.cloudfoundry.client.v3.processes.HealthCheck.builder() - .type( - HealthCheckType.PROCESS) - .build()) - .build()) - .block(); CloudProcess cloudProcess = client.getApplicationProcess(applicationGuid); - assertEquals(org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType.PROCESS, - cloudProcess.getHealthCheckType()); + assertEquals(HealthCheckType.PROCESS, cloudProcess.getHealthCheckType()); } catch (Exception e) { fail(e); } finally { @@ -397,8 +383,8 @@ void getBuildsForApplication() { @DisplayName("Create application and update its metadata") void updateApplicationMetadata() { String applicationName = "test-application-15"; - Metadata metadata = Metadata.builder() - .label("test-app", "test-app") + Metadata metadata = ImmutableMetadata.builder() + .putLabel("test-app", "test-app") .build(); try { createAndVerifyDefaultApplication(applicationName); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java index b394252229..9c7ea635fe 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java @@ -7,15 +7,16 @@ import java.util.HashMap; import java.util.Map; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableCloudFoundryClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableLifecycle; import org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle; import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudSpaceClient; +import org.cloudfoundry.multiapps.controller.client.facade.rest.ImmutableCloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; -import org.cloudfoundry.client.CloudFoundryClient; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -30,7 +31,6 @@ abstract class CloudControllerClientIntegrationTest { protected static final String DEFAULT_STACK = "cflinuxfs4"; protected static CloudControllerClient client; - protected static CloudFoundryClient delegate; protected static CloudSpace target; @BeforeAll @@ -39,14 +39,14 @@ static void login() throws MalformedURLException { CloudCredentials credentials = getCloudCredentials(); URL apiUrl = URI.create(ITVariable.CF_API.getValue()) .toURL(); - var clientFactory = ImmutableCloudFoundryClientFactory.builder() - .build(); - var oauthClient = new RestUtil().createOAuthClientByControllerUrl(apiUrl, true); + CloudControllerRestClientFactory clientFactory = ImmutableCloudControllerRestClientFactory.builder() + .shouldTrustSelfSignedCertificates(true) + .build(); + OAuthClient oauthClient = new RestUtil().createOAuthClientByControllerUrl(apiUrl, true); oauthClient.init(credentials); CloudSpaceClient spaceClient = clientFactory.createSpaceClient(apiUrl, oauthClient, Collections.emptyMap()); target = spaceClient.getSpace(ITVariable.ORG.getValue(), ITVariable.SPACE.getValue()); client = new CloudControllerClientImpl(apiUrl, credentials, target, true); - delegate = clientFactory.createClient(apiUrl, oauthClient, Collections.emptyMap()); } @BeforeEach @@ -68,7 +68,7 @@ protected Lifecycle createLifecycle(Staging staging) { .build(); } - var data = new HashMap(); + HashMap data = new HashMap(); data.put("buildpacks", staging.getBuildpacks()); data.put("stack", DEFAULT_STACK); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ServicesCloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ServicesCloudControllerClientIntegrationTest.java index c0de6f9023..0cc22fe179 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ServicesCloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ServicesCloudControllerClientIntegrationTest.java @@ -23,8 +23,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.jobs.JobState; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProviderTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProviderTest.java deleted file mode 100644 index f6131944b1..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProviderTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuth2AccessTokenWithAdditionalInfo; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; -import org.cloudfoundry.reactor.ConnectionContext; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import reactor.core.publisher.Mono; - -class OAuthTokenProviderTest { - - @Mock - private OAuthClient oAuthClient; - @Mock - private OAuth2AccessTokenWithAdditionalInfo token; - @Mock - private ConnectionContext connectionContext; - - private OAuthTokenProvider provider; - - @BeforeEach - void setUp() throws Exception { - MockitoAnnotations.openMocks(this) - .close(); - provider = new OAuthTokenProvider(oAuthClient); - } - - @Test - void testGetTokenIsLazyAndDoesNotInvokeOAuthClientUntilSubscribed() { - Mockito.when(oAuthClient.getToken()) - .thenReturn(token); - Mockito.when(token.getAuthorizationHeaderValue()) - .thenReturn("Bearer abc123"); - - Mono mono = provider.getToken(connectionContext); - - Mockito.verify(oAuthClient, Mockito.never()) - .getToken(); - - String result = mono.block(); - - Assertions.assertEquals("Bearer abc123", result); - Mockito.verify(oAuthClient) - .getToken(); - } - - @Test - void testGetTokenInvokesOAuthClientOncePerSubscription() { - Mockito.when(oAuthClient.getToken()) - .thenReturn(token); - Mockito.when(token.getAuthorizationHeaderValue()) - .thenReturn("Bearer abc123"); - - Mono mono = provider.getToken(connectionContext); - - mono.block(); - mono.block(); - - Mockito.verify(oAuthClient, Mockito.times(2)) - .getToken(); - } - - @Test - void testGetTokenPropagatesOAuthClientFailure() { - RuntimeException failure = new RuntimeException("token retrieval failed"); - Mockito.when(oAuthClient.getToken()) - .thenThrow(failure); - - Mono mono = provider.getToken(connectionContext); - - RuntimeException thrown = Assertions.assertThrows(RuntimeException.class, mono::block); - Assertions.assertSame(failure, thrown); - } -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java deleted file mode 100644 index 514fe959c6..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; -import java.util.Map; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableLifecycle; -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.DockerData; -import org.cloudfoundry.client.v3.Lifecycle; -import org.cloudfoundry.client.v3.LifecycleType; -import org.cloudfoundry.client.v3.applications.ApplicationResource; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.junit.jupiter.api.Test; - -class RawCloudApplicationTest { - - private static final String STATE = "STARTED"; - private static final String BUILDPACK = "ruby_buildpack"; - private static final String STACK_NAME = "cflinuxfs3"; - private static final String SPACE_NAME = "test"; - private static final CloudSpace SPACE = ImmutableCloudSpace.builder() - .name(SPACE_NAME) - .build(); - private static final String EXPECTED_BUILDPACK = "ruby_buildpack"; - private static final String BUILDPACK_URL = "custom-buildpack-url"; - - private static final String EXPECTED_STACK = "cflinuxfs3"; - private static final CloudApplication.State EXPECTED_STATE = CloudApplication.State.STARTED; - - @Test - void testDeriveForBuildpackApp() { - RawCloudEntityTest.testDerive(buildApplication(buildBuildpackLifecycle()), buildRawApplication(buildBuildpackLifecycleResource())); - } - - @Test - void testDeriveForDockerApp() { - RawCloudEntityTest.testDerive(buildApplication(buildDockerLifecycle()), buildRawApplication(buildDockerLifecycleResource())); - } - - @Test - void testDeriveForCnbApp() { - RawCloudEntityTest.testDerive(buildApplication(buildCnbLifecycle()), buildRawApplication(buildCnbLifecycleResource())); - } - - private static CloudApplication buildApplication(org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle lifecycle) { - return ImmutableCloudApplication.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .v3Metadata(RawCloudEntityTest.V3_METADATA) - .name(RawCloudEntityTest.NAME) - .state(EXPECTED_STATE) - .lifecycle(lifecycle) - .space(SPACE) - .build(); - } - - private static org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle buildBuildpackLifecycle() { - return ImmutableLifecycle.builder() - .type(org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType.BUILDPACK) - .data(buildLifecycleData(EXPECTED_BUILDPACK)) - .build(); - } - - private org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle buildCnbLifecycle() { - return ImmutableLifecycle.builder() - .type(org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType.CNB) - .data(buildLifecycleData(BUILDPACK_URL)) - .build(); - } - - private static org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle buildDockerLifecycle() { - return ImmutableLifecycle.builder() - .type(org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType.DOCKER) - .data(Map.of()) - .build(); - } - - private static Map buildLifecycleData(String buildpack) { - return Map.of("buildpacks", List.of(buildpack), "stack", EXPECTED_STACK); - } - - private static RawCloudApplication buildRawApplication(Lifecycle lifecycle) { - return ImmutableRawCloudApplication.builder() - .application(buildApplicationResource(lifecycle)) - .space(SPACE) - .build(); - } - - private static ApplicationResource buildApplicationResource(Lifecycle lifecycle) { - return ApplicationResource.builder() - .metadata(RawCloudEntityTest.V3_METADATA) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .state(ApplicationState.valueOf(STATE)) - .id(RawCloudEntityTest.GUID.toString()) - .lifecycle(lifecycle) - .name("foo") - .build(); - } - - private static Lifecycle buildBuildpackLifecycleResource() { - return Lifecycle.builder() - .type(LifecycleType.BUILDPACK) - .data(BuildpackData.builder() - .buildpack(BUILDPACK) - .stack(STACK_NAME) - .build()) - .build(); - } - - private Lifecycle buildCnbLifecycleResource() { - return Lifecycle.builder() - .type(LifecycleType.CNB) - .data(BuildpackData.builder() - .buildpack(BUILDPACK_URL) - .stack(STACK_NAME) - .build()) - .build(); - } - - private static Lifecycle buildDockerLifecycleResource() { - return Lifecycle.builder() - .type(LifecycleType.DOCKER) - .data(DockerData.builder() - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java deleted file mode 100644 index 1340e590ff..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; - -import org.cloudfoundry.client.v3.Error; -import org.cloudfoundry.client.v3.jobs.GetJobResponse; -import org.cloudfoundry.client.v3.jobs.Job; -import org.cloudfoundry.client.v3.jobs.JobState; -import org.cloudfoundry.client.v3.jobs.Warning; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; - -class RawCloudAsyncJobTest { - - private static final String OPERATION = "service_broker.delete"; - private static final String WARNING_1 = "warning1"; - private static final String WARNING_2 = "warning2"; - private static final String WARNINGS = "warning1,warning2"; - private static final int ERROR_CODE_1 = 10008; - private static final int ERROR_CODE_2 = 1000; - private static final String ERROR_TITLE_1 = "CF-UnprocessableEntity"; - private static final String ERROR_TITLE_2 = "CF-InvalidAuthToken"; - private static final String ERROR_DETAIL_1 = "something went wrong"; - private static final String ERROR_DETAIL_2 = "not valid token"; - private static final String ERRORS = "10008 CF-UnprocessableEntity something went wrong,1000 CF-InvalidAuthToken not valid token"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedTask(), buildRawTask()); - } - - private CloudAsyncJob buildExpectedTask() { - return ImmutableCloudAsyncJob.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .state(JobState.FAILED) - .operation(OPERATION) - .warnings(WARNINGS) - .errors(ERRORS) - .build(); - } - - private RawCloudAsyncJob buildRawTask() { - return ImmutableRawCloudAsyncJob.of(buildTestResource()); - } - - private Job buildTestResource() { - return GetJobResponse.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .state(JobState.FAILED) - .operation(OPERATION) - .warnings(buildWarnings()) - .errors(buildErrors()) - .build(); - } - - private List buildWarnings() { - return List.of(Warning.builder() - .detail(WARNING_1) - .build(), - Warning.builder() - .detail(WARNING_2) - .build()); - } - - private List buildErrors() { - return List.of(Error.builder() - .code(ERROR_CODE_1) - .title(ERROR_TITLE_1) - .detail(ERROR_DETAIL_1) - .build(), - Error.builder() - .code(ERROR_CODE_2) - .title(ERROR_TITLE_2) - .detail(ERROR_DETAIL_2) - .build()); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuildTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuildTest.java deleted file mode 100644 index c35ec79de0..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuildTest.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.UUID; - -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.Lifecycle; -import org.cloudfoundry.client.v3.LifecycleType; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.builds.Build; -import org.cloudfoundry.client.v3.builds.BuildResource; -import org.cloudfoundry.client.v3.builds.BuildState; -import org.cloudfoundry.client.v3.builds.CreatedBy; -import org.cloudfoundry.client.v3.builds.Droplet; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; - -class RawCloudBuildTest { - - private static final String PACKAGE_GUID_STRING = "d0bd1437-f112-48cd-b777-6b160cf2fff0"; - private static final String DROPLET_GUID_STRING = "47e172c9-bfb2-41df-bf3f-850108c241ef"; - private static final Droplet DROPLET = buildTestDroplet(); - private static final String CREATED_BY_GUID_STRING = "37964fd8-a234-4c04-af31-c3fe5e477bed"; - private static final String CREATED_BY_NAME = "admin"; - private static final LifecycleType LIFECYCLE_TYPE = LifecycleType.BUILDPACK; - private static final BuildState STATE = BuildState.FAILED; - private static final String ERROR = "blabla"; - - private static final UUID PACKAGE_GUID = UUID.fromString(PACKAGE_GUID_STRING); - private static final UUID DROPLET_GUID = UUID.fromString(DROPLET_GUID_STRING); - private static final UUID CREATED_BY_GUID = UUID.fromString(CREATED_BY_GUID_STRING); - - private static final CloudBuild.State EXPECTED_STATE = CloudBuild.State.FAILED; - private static final DropletInfo EXPECTED_DROPLET_INFO = buildExpectedDropletInfo(); - - private static CloudBuild buildExpectedBuild() { - return buildExpectedBuild(null); - } - - private static CloudBuild buildExpectedBuild(DropletInfo dropletInfo) { - return ImmutableCloudBuild.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .createdBy(buildExpectedCreatedBy()) - .dropletInfo(dropletInfo) - .packageInfo(buildExpectedPackageInfo()) - .state(EXPECTED_STATE) - .error(ERROR) - .build(); - } - - private static CloudBuild.CreatedBy buildExpectedCreatedBy() { - return ImmutableCloudBuild.ImmutableCreatedBy.builder() - .guid(CREATED_BY_GUID) - .name(CREATED_BY_NAME) - .build(); - } - - private static DropletInfo buildExpectedDropletInfo() { - return ImmutableDropletInfo.builder() - .guid(DROPLET_GUID) - .build(); - } - - private static CloudBuild.PackageInfo buildExpectedPackageInfo() { - return ImmutableCloudBuild.ImmutablePackageInfo.builder() - .guid(PACKAGE_GUID) - .build(); - } - - private static Build buildTestResource(Droplet droplet) { - return BuildResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .inputPackage(buildTestPackage()) - .createdBy(buildTestCreatedBy()) - .lifecycle(buildTestLifecycle()) - .droplet(droplet) - .state(STATE) - .error(ERROR) - .build(); - } - - private static Relationship buildTestPackage() { - return Relationship.builder() - .id(PACKAGE_GUID_STRING) - .build(); - } - - private static Droplet buildTestDroplet() { - return Droplet.builder() - .id(DROPLET_GUID_STRING) - .build(); - } - - private static CreatedBy buildTestCreatedBy() { - return CreatedBy.builder() - .id(CREATED_BY_GUID_STRING) - .name(CREATED_BY_NAME) - .email(CREATED_BY_NAME) - .build(); - } - - private static Lifecycle buildTestLifecycle() { - return Lifecycle.builder() - .data(buildTestLifecycleData()) - .type(LIFECYCLE_TYPE) - .build(); - } - - private static BuildpackData buildTestLifecycleData() { - return BuildpackData.builder() - .build(); - } - - @Test - void testDeriveWithoutDroplet() { - RawCloudEntityTest.testDerive(buildExpectedBuild(), buildRawBuild()); - } - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedBuild(EXPECTED_DROPLET_INFO), buildRawBuild(DROPLET)); - } - - private RawCloudBuild buildRawBuild() { - return buildRawBuild(null); - } - - private RawCloudBuild buildRawBuild(Droplet droplet) { - return ImmutableRawCloudBuild.of(buildTestResource(droplet)); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomainTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomainTest.java deleted file mode 100644 index 65fd5a6642..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomainTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.ToManyRelationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.domains.Domain; -import org.cloudfoundry.client.v3.domains.DomainRelationships; -import org.cloudfoundry.client.v3.organizations.GetOrganizationDefaultDomainResponse; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; - -class RawCloudDomainTest { - - private static final String DOMAIN_NAME = "example.com"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedDomain(), buildRawDomain()); - } - - private static CloudDomain buildExpectedDomain() { - return ImmutableCloudDomain.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(DOMAIN_NAME) - .build(); - } - - private static RawCloudDomain buildRawDomain() { - return ImmutableRawCloudDomain.of(buildTestResource()); - } - - private static Domain buildTestResource() { - return GetOrganizationDefaultDomainResponse.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .name(DOMAIN_NAME) - .isInternal(false) - .relationships(DomainRelationships.builder() - .organization(ToOneRelationship.builder() - .build()) - .sharedOrganizations(ToManyRelationship.builder() - .build()) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java deleted file mode 100644 index 51b1fb5007..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java +++ /dev/null @@ -1,151 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.cloudfoundry.client.v3.organizations.Organization; -import org.cloudfoundry.client.v3.organizations.OrganizationResource; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; - -class RawCloudEntityTest { - - static final String GUID_STRING = "3725650a-8725-4401-a949-c68f83d54a86"; - static final String CREATED_AT_STRING = "2017-06-22T13:38:41Z"; - static final String UPDATED_AT_STRING = "2019-03-21T12:29:24Z"; - - static final String NAME = "foo"; - static final UUID GUID = UUID.fromString(GUID_STRING); - static final LocalDateTime CREATED_AT = fromZonedDateTime(ZonedDateTime.of(2017, 6, 22, 13, 38, 41, 0, ZoneId.of("Z"))); - static final LocalDateTime UPDATED_AT = fromZonedDateTime(ZonedDateTime.of(2019, 3, 21, 12, 29, 24, 0, ZoneId.of("Z"))); - static final Map V3_ANNOTATIONS = Map.of("annotation1", "value1", "annotation2", "value2"); - static final Map V3_LABELS = Map.of("label1", "value1", "label2", "value2"); - - static final Metadata V3_METADATA = Metadata.builder() - .annotations(V3_ANNOTATIONS) - .labels(V3_LABELS) - .build(); - - static final CloudMetadata EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE = ImmutableCloudMetadata.builder() - .guid(GUID) - .createdAt(CREATED_AT) - .updatedAt(UPDATED_AT) - .build(); - static final CloudMetadata EXPECTED_METADATA_V3 = ImmutableCloudMetadata.builder() - .guid(GUID) - .createdAt(CREATED_AT) - .updatedAt(UPDATED_AT) - .build(); - - static LocalDateTime fromZonedDateTime(ZonedDateTime dateTime) { - return dateTime.toLocalDateTime(); - } - - @Test - void testParseV3ResourceMetadata() { - Organization resource = OrganizationResource.builder() - .id(GUID_STRING) - .createdAt(CREATED_AT_STRING) - .updatedAt(UPDATED_AT_STRING) - .name(NAME) - .metadata(V3_METADATA) - .build(); - - CloudMetadata metadata = RawCloudEntity.parseResourceMetadata(resource); - assertEquals(EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE, metadata); - } - - @Test - void testParseNullableGuid() { - assertNull(RawCloudEntity.parseNullableGuid(null)); - } - - @Test - void testParseNullableDate() { - assertNull(RawCloudEntity.parseNullableDate(null)); - } - - @Test - void testParseGuid() { - assertEquals(GUID, RawCloudEntity.parseGuid(GUID_STRING)); - } - - @Test - void testParseGuidWithInvalidGuid() { - assertNull(RawCloudEntity.parseGuid("foo")); - } - - @Test - void testParseDateWithInvalidDate() { - assertNull(RawCloudEntity.parseDate("foo")); - } - - @Test - void testParseDateWithInvalidFormat() { - assertNull(RawCloudEntity.parseDate("16.07.2019 15:30:25")); - } - - @Test - void testParseDate() { - assertEquals(CREATED_AT, RawCloudEntity.parseDate(CREATED_AT_STRING)); - } - - @Test - void testParseEnum() { - CloudApplication.State state = RawCloudEntity.parseEnum(ApplicationState.STARTED, CloudApplication.State.class); - assertEquals(CloudApplication.State.STARTED, state); - } - - @Test - void testParseEnumWithIncompatibleEnumTypes() { - Assertions.assertThrows(IllegalArgumentException.class, - () -> RawCloudEntity.parseEnum(ApplicationState.STARTED, CloudBuild.State.class)); - } - - @Test - void testDeriveFromNullable() { - assertEquals(NAME, RawCloudEntity.deriveFromNullable(() -> NAME)); - } - - @Test - void testDeriveFromNullableWithNull() { - assertNull(RawCloudEntity.deriveFromNullable(null)); - } - - @Test - void testDerive() { - List> derivables = Arrays.asList(() -> NAME, () -> GUID_STRING); - assertEquals(Arrays.asList(NAME, GUID_STRING), RawCloudEntity.derive(derivables)); - } - - static void testDerive(T expected, Derivable derivable) { - assertEquals(expected, derivable.derive()); - } - - static ToOneRelationship buildToOneRelationship(String id) { - return ToOneRelationship.builder() - .data(Relationship.builder() - .id(id) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEventTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEventTest.java deleted file mode 100644 index 2a826d312a..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEventTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.UUID; - -import org.cloudfoundry.client.v3.auditevents.AuditEventActor; -import org.cloudfoundry.client.v3.auditevents.AuditEventResource; -import org.cloudfoundry.client.v3.auditevents.AuditEventTarget; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent.Participant; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent.ImmutableParticipant; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; - -class RawCloudEventTest { - - private static final String TARGET_GUID_STRING = "b7c57058-afc0-43c9-afee-64019e850bef"; - private static final String TARGET_NAME = "foo"; - private static final String TARGET_TYPE = "app"; - private static final String ACTOR_GUID_STRING = "72c1e48a-9629-4cfe-a64c-f53851d81f61"; - private static final String ACTOR_NAME = "john"; - private static final String ACTOR_TYPE = "user"; - private static final String TIMESTAMP_STRING = "2019-07-03T20:00:46Z"; - private static final String TYPE = "audit.app.create"; - - private static final UUID TARGET_GUID = UUID.fromString(TARGET_GUID_STRING); - private static final UUID ACTOR_GUID = UUID.fromString(ACTOR_GUID_STRING); - private static final LocalDateTime TIMESTAMP = RawCloudEntityTest.fromZonedDateTime(ZonedDateTime.of(2019, 7, 3, 20, 0, 46, 0, - ZoneId.of("Z"))); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedEvent(), buildRawEvent()); - } - - @Test - void testDeriveWithEmptyResponse() { - RawCloudEntityTest.testDerive(buildEmptyExpectedEvent(), buildEmptyRawEvent()); - } - - private CloudEvent buildExpectedEvent() { - return ImmutableCloudEvent.builder() - .metadata(ImmutableCloudMetadata.builder() - .guid(RawCloudEntityTest.GUID) - .createdAt(TIMESTAMP) - .updatedAt(RawCloudEntityTest.UPDATED_AT) - .build()) - .target(buildExpectedTarget()) - .actor(buildExpectedActor()) - .type(TYPE) - .build(); - } - - private Participant buildExpectedTarget() { - return ImmutableParticipant.builder() - .guid(TARGET_GUID) - .name(TARGET_NAME) - .type(TARGET_TYPE) - .build(); - } - - private Participant buildExpectedActor() { - return ImmutableParticipant.builder() - .guid(ACTOR_GUID) - .name(ACTOR_NAME) - .type(ACTOR_TYPE) - .build(); - } - - private CloudEvent buildEmptyExpectedEvent() { - return ImmutableCloudEvent.builder() - .metadata(ImmutableCloudMetadata.builder() - .guid(RawCloudEntityTest.GUID) - .createdAt(TIMESTAMP) - .updatedAt(RawCloudEntityTest.UPDATED_AT) - .build()) - .target(buildEmptyParticipant()) - .actor(buildEmptyParticipant()) - .build(); - } - - private Participant buildEmptyParticipant() { - return ImmutableParticipant.builder() - .build(); - } - - private RawCloudEvent buildRawEvent() { - return ImmutableRawCloudEvent.of(buildTestResource()); - } - - private AuditEventResource buildTestResource() { - return AuditEventResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(TIMESTAMP_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .type(TYPE) - .auditEventActor(AuditEventActor.builder() - .id(ACTOR_GUID_STRING) - .name(ACTOR_NAME) - .type(ACTOR_TYPE) - .build()) - .auditEventTarget(AuditEventTarget.builder() - .id(TARGET_GUID_STRING) - .type(TARGET_TYPE) - .name(TARGET_NAME) - .build()) - .build(); - } - - private RawCloudEvent buildEmptyRawEvent() { - return ImmutableRawCloudEvent.of(buildEmptyTestResource()); - } - - private AuditEventResource buildEmptyTestResource() { - return AuditEventResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(TIMESTAMP_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackageTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackageTest.java deleted file mode 100644 index 2f636ea79b..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackageTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Checksum; -import org.cloudfoundry.client.v3.ChecksumType; -import org.cloudfoundry.client.v3.packages.BitsData; -import org.cloudfoundry.client.v3.packages.DockerData; -import org.cloudfoundry.client.v3.packages.Package; -import org.cloudfoundry.client.v3.packages.PackageData; -import org.cloudfoundry.client.v3.packages.PackageResource; -import org.cloudfoundry.client.v3.packages.PackageState; -import org.cloudfoundry.client.v3.packages.PackageType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableBitsData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; - -class RawCloudPackageTest { - - private static final PackageState STATE = PackageState.EXPIRED; - private static final String ERROR = "blabla"; - private static final String DOCKER_IMAGE = "cloudfoundry/test"; - private static final ChecksumType CHECKSUM_TYPE = ChecksumType.SHA256; - private static final String CHECKSUM_VALUE = "7251a608605c0f45710f8415bba0d117c90598824697a2a5d00850ea9b179112"; - - private static final Status EXPECTED_STATUS = Status.EXPIRED; - private static final String EXPECTED_CHECKSUM_ALGORITHM = CHECKSUM_TYPE.toString(); - - @Test - void testDeriveWithBitsData() { - RawCloudEntityTest.testDerive(buildExpectedPackage(CloudPackage.Type.BITS), buildRawPackage(PackageType.BITS)); - } - - @Test - void testDeriveWithDockerData() { - RawCloudEntityTest.testDerive(buildExpectedPackage(CloudPackage.Type.DOCKER), buildRawPackage(PackageType.DOCKER)); - } - - private static CloudPackage buildExpectedPackage(CloudPackage.Type type) { - return ImmutableCloudPackage.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .data(buildExpectedData(type)) - .type(type) - .status(EXPECTED_STATUS) - .build(); - } - - private static CloudPackage.PackageData buildExpectedData(CloudPackage.Type type) { - if (type == CloudPackage.Type.DOCKER) { - return ImmutableDockerData.builder() - .image(DOCKER_IMAGE) - .build(); - } - return ImmutableBitsData.builder() - .checksum(buildExpectedChecksum()) - .error(ERROR) - .build(); - } - - private static org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData.Checksum buildExpectedChecksum() { - return ImmutableBitsData.ImmutableChecksum.builder() - .algorithm(EXPECTED_CHECKSUM_ALGORITHM) - .value(CHECKSUM_VALUE) - .build(); - } - - private static RawCloudPackage buildRawPackage(PackageType type) { - return ImmutableRawCloudPackage.builder() - .resource(buildTestResource(type)) - .build(); - } - - private static Package buildTestResource(PackageType type) { - return PackageResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .type(type) - .data(buildTestData(type)) - .state(STATE) - .build(); - } - - private static PackageData buildTestData(PackageType type) { - if (type == PackageType.DOCKER) { - return buildDockerData(); - } - return buildBitsData(); - } - - private static DockerData buildDockerData() { - return DockerData.builder() - .image(DOCKER_IMAGE) - .build(); - } - - private static BitsData buildBitsData() { - return BitsData.builder() - .checksum(buildTestChecksum()) - .error(ERROR) - .build(); - } - - private static Checksum buildTestChecksum() { - return Checksum.builder() - .type(CHECKSUM_TYPE) - .value(CHECKSUM_VALUE) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java deleted file mode 100644 index b57652433c..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.Data; -import org.cloudfoundry.client.v3.processes.HealthCheck; -import org.cloudfoundry.client.v3.processes.HealthCheckType; -import org.cloudfoundry.client.v3.processes.ProcessRelationships; -import org.cloudfoundry.client.v3.processes.ProcessResource; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheck; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudProcess; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -class RawCloudProcessTest { - - private static final String COMMAND = "bundle exec rackup"; - private static final Integer DISK_IN_MB = 1024; - private static final Integer INSTANCES = 2; - private static final Integer MEMORY_IN_MB = 512; - private static final String HTTP_ENDPOINT = "/health"; - private static final Integer TIMEOUT = 30; - private static final Integer INVOCATION_TIMEOUT = 5; - private static final Integer INTERVAL = 10; - private static final String READINESS_HTTP_ENDPOINT = "/ready"; - private static final Integer READINESS_INVOCATION_TIMEOUT = 4; - private static final Integer READINESS_INTERVAL = 8; - - @Test - void testDeriveWithFullHealthCheckDataIncludingInterval() { - RawCloudProcess raw = ImmutableRawCloudProcess.of(buildProcessResource(HealthCheckType.HTTP, buildHealthCheckData(), - ReadinessHealthCheckType.HTTP, - buildReadinessHealthCheckData())); - - CloudProcess derived = raw.derive(); - - assertEquals(buildExpected(org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType.HTTP, INTERVAL, - ReadinessHealthCheckType.HTTP.getValue()), - derived); - } - - @Test - void testDeriveWithMissingHealthCheckDataLeavesIntervalNull() { - // healthCheck.getData() returns null -> all four health-check Integer/String fields stay null - RawCloudProcess raw = ImmutableRawCloudProcess.of(buildProcessResource(HealthCheckType.PORT, null, - ReadinessHealthCheckType.PORT, null)); - - CloudProcess derived = raw.derive(); - - assertEquals(org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType.PORT, derived.getHealthCheckType()); - assertNull(derived.getHealthCheckTimeout()); - assertNull(derived.getHealthCheckInvocationTimeout()); - assertNull(derived.getHealthCheckHttpEndpoint()); - assertNull(derived.getHealthCheckInterval()); - assertNull(derived.getReadinessHealthCheckInvocationTimeout()); - assertNull(derived.getReadinessHealthCheckHttpEndpoint()); - assertNull(derived.getReadinessHealthCheckInterval()); - } - - @Test - void testDeriveWithHealthCheckDataPresentButIntervalNull() { - Data dataWithoutInterval = Data.builder() - .endpoint(HTTP_ENDPOINT) - .timeout(TIMEOUT) - .invocationTimeout(INVOCATION_TIMEOUT) - .build(); - RawCloudProcess raw = ImmutableRawCloudProcess.of(buildProcessResource(HealthCheckType.HTTP, dataWithoutInterval, - ReadinessHealthCheckType.PORT, null)); - - CloudProcess derived = raw.derive(); - - assertEquals(TIMEOUT, derived.getHealthCheckTimeout()); - assertEquals(INVOCATION_TIMEOUT, derived.getHealthCheckInvocationTimeout()); - assertEquals(HTTP_ENDPOINT, derived.getHealthCheckHttpEndpoint()); - assertNull(derived.getHealthCheckInterval()); - } - - private static ProcessResource buildProcessResource(HealthCheckType healthCheckType, Data healthCheckData, - ReadinessHealthCheckType readinessHealthCheckType, Data readinessHealthCheckData) { - HealthCheck.Builder healthCheckBuilder = HealthCheck.builder() - .type(healthCheckType); - if (healthCheckData != null) { - healthCheckBuilder.data(healthCheckData); - } - ReadinessHealthCheck.Builder readinessBuilder = ReadinessHealthCheck.builder() - .type(readinessHealthCheckType); - if (readinessHealthCheckData != null) { - readinessBuilder.data(readinessHealthCheckData); - } - return ProcessResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .command(COMMAND) - .diskInMb(DISK_IN_MB) - .instances(INSTANCES) - .memoryInMb(MEMORY_IN_MB) - .type("web") - .metadata(Metadata.builder() - .build()) - .relationships(ProcessRelationships.builder() - .build()) - .healthCheck(healthCheckBuilder.build()) - .readinessHealthCheck(readinessBuilder.build()) - .build(); - } - - private static Data buildHealthCheckData() { - return Data.builder() - .endpoint(HTTP_ENDPOINT) - .timeout(TIMEOUT) - .invocationTimeout(INVOCATION_TIMEOUT) - .interval(INTERVAL) - .build(); - } - - private static Data buildReadinessHealthCheckData() { - return Data.builder() - .endpoint(READINESS_HTTP_ENDPOINT) - .invocationTimeout(READINESS_INVOCATION_TIMEOUT) - .interval(READINESS_INTERVAL) - .build(); - } - - private static CloudProcess buildExpected(org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType type, - Integer interval, String readinessHealthCheckType) { - return ImmutableCloudProcess.builder() - .command(COMMAND) - .instances(INSTANCES) - .memoryInMb(MEMORY_IN_MB) - .diskInMb(DISK_IN_MB) - .healthCheckType(type) - .healthCheckHttpEndpoint(HTTP_ENDPOINT) - .healthCheckTimeout(TIMEOUT) - .healthCheckInvocationTimeout(INVOCATION_TIMEOUT) - .healthCheckInterval(interval) - .readinessHealthCheckType(readinessHealthCheckType) - .readinessHealthCheckHttpEndpoint(READINESS_HTTP_ENDPOINT) - .readinessHealthCheckInvocationTimeout(READINESS_INVOCATION_TIMEOUT) - .readinessHealthCheckInterval(READINESS_INTERVAL) - .build(); - } -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRouteTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRouteTest.java deleted file mode 100644 index 94f19d18d3..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRouteTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; -import java.util.UUID; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.routes.Application; -import org.cloudfoundry.client.v3.routes.Destination; -import org.cloudfoundry.client.v3.routes.Route; -import org.cloudfoundry.client.v3.routes.RouteRelationships; -import org.cloudfoundry.client.v3.routes.RouteResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; - -class RawCloudRouteTest { - - private static final String HOST = "foo"; - private static final String DOMAIN_NAME = "example.com"; - private static final CloudDomain DOMAIN = buildTestDomain(); - private static final Integer APPS_USING_ROUTE = 2; - private static final UUID DESTINATION_1_GUID = UUID.randomUUID(); - private static final UUID APPLICATION_1_GUID = UUID.randomUUID(); - private static final String DESTINATION_1_PROTOCOL = "http2"; - private static final UUID DESTINATION_2_GUID = UUID.randomUUID(); - private static final UUID APPLICATION_2_GUID = UUID.randomUUID(); - private static final String DESTINATION_2_PROTOCOL = "http1"; - private static final List DESTINATIONS = buildTestDestinations(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedRoute(), buildRawRoute()); - } - - private static CloudRoute buildExpectedRoute() { - return ImmutableCloudRoute.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .host(HOST) - .domain(DOMAIN) - .path("") - .appsUsingRoute(APPS_USING_ROUTE) - .url(HOST + "." + DOMAIN_NAME) - .build(); - } - - private static RawCloudRoute buildRawRoute() { - return ImmutableRawCloudRoute.builder() - .route(buildTestRoute()) - .build(); - } - - private static Route buildTestRoute() { - return RouteResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .relationships(RouteRelationships.builder() - .space(buildToOneRelationship(RawCloudEntityTest.GUID)) - .domain(buildToOneRelationship(RawCloudEntityTest.GUID)) - .build()) - .metadata(RawCloudEntityTest.V3_METADATA) - .host(HOST) - .path("") - .url(HOST + "." + DOMAIN_NAME) - .addAllDestinations(DESTINATIONS) - .build(); - } - - private static CloudDomain buildTestDomain() { - return ImmutableCloudDomain.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .name(DOMAIN_NAME) - .build(); - } - - private static List buildTestDestinations() { - return List.of(buildDestination(DESTINATION_1_GUID.toString(), APPLICATION_1_GUID.toString(), DESTINATION_1_PROTOCOL), - buildDestination(DESTINATION_2_GUID.toString(), APPLICATION_2_GUID.toString(), DESTINATION_2_PROTOCOL)); - } - - private static Destination buildDestination(String destinationGuid, String applicationGuid, String protocol) { - return Destination.builder() - .destinationId(destinationGuid) - .application(Application.builder() - .applicationId(applicationGuid) - .build()) - .protocol(protocol) - .build(); - } - - private static ToOneRelationship buildToOneRelationship(UUID guid) { - return ToOneRelationship.builder() - .data(buildRelationship(guid)) - .build(); - } - - private static Relationship buildRelationship(UUID guid) { - return Relationship.builder() - .id(guid.toString()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java deleted file mode 100644 index 41d468060a..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.time.LocalDateTime; -import java.util.UUID; - -import org.cloudfoundry.client.v3.LastOperation; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingRelationships; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; - -class RawCloudServiceBindingTest { - - private static final String APPLICATION_GUID_STRING = "3725650a-8725-4401-a949-c68f83d54a86"; - private static final String SERVICE_INSTANCE_GUID_STRING = "3725650a-8725-4401-a949-c68f83d54a86"; - private static final UUID APPLICATION_GUID = UUID.fromString(APPLICATION_GUID_STRING); - private static final UUID SERVICE_INSTANCE_GUID = UUID.fromString(SERVICE_INSTANCE_GUID_STRING); - private static final LastOperation LAST_OPERATION = buildLastOperation(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceBinding(), buildRawServiceBinding()); - } - - private static CloudServiceBinding buildExpectedServiceBinding() { - return ImmutableCloudServiceBinding.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .applicationGuid(APPLICATION_GUID) - .serviceInstanceGuid(SERVICE_INSTANCE_GUID) - .serviceBindingOperation(ServiceCredentialBindingOperation.from(LAST_OPERATION)) - .build(); - } - - private static RawCloudServiceBinding buildRawServiceBinding() { - return ImmutableRawCloudServiceBinding.builder() - .serviceBinding(buildTestResource()) - - .build(); - } - - private static ServiceBindingResource buildTestResource() { - return ServiceBindingResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .type(ServiceBindingType.APPLICATION) - .lastOperation(LAST_OPERATION) - .relationships(ServiceBindingRelationships.builder() - .serviceInstance(RawCloudEntityTest.buildToOneRelationship(SERVICE_INSTANCE_GUID_STRING)) - .application(RawCloudEntityTest.buildToOneRelationship(APPLICATION_GUID_STRING)) - .build()) - .build(); - } - - private static LastOperation buildLastOperation() { - return LastOperation.builder() - .state(ServiceCredentialBindingOperation.State.SUCCEEDED.toString()) - .type(ServiceCredentialBindingOperation.Type.CREATE.toString()) - .createdAt(LocalDateTime.now() - .toString()) - .updatedAt(LocalDateTime.now() - .toString()) - .description("Service binding created") - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBrokerTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBrokerTest.java deleted file mode 100644 index c3c9dca252..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBrokerTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerRelationships; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; - -class RawCloudServiceBrokerTest { - - private static final String NAME = "auditlog-broker"; - private static final String URL = "https://auditlog-broker.example.com"; - private static final String SPACE_GUID_STRING = "ef93547f-74c3-4bad-ba69-b7dc4f212622"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceBroker(), buildRawServiceBroker()); - } - - private static CloudServiceBroker buildExpectedServiceBroker() { - return ImmutableCloudServiceBroker.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .spaceGuid(SPACE_GUID_STRING) - .url(URL) - .build(); - } - - private static RawCloudServiceBroker buildRawServiceBroker() { - return ImmutableRawCloudServiceBroker.builder() - .serviceBroker(buildTestServiceBroker()) - .build(); - } - - private static ServiceBrokerResource buildTestServiceBroker() { - return ServiceBrokerResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .name(NAME) - .url(URL) - .relationships(ServiceBrokerRelationships.builder() - .space(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SPACE_GUID_STRING) - .build()) - .build()) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java deleted file mode 100644 index 9645cca49a..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.cloudfoundry.client.v3.LastOperation; -import org.cloudfoundry.client.v3.MaintenanceInfo; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceRelationships; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceResource; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; - -class RawCloudServiceInstanceTest { - - private static final String SERVICE_INSTANCE_ID = "7cb26016-342d-11ed-a261-0242ac120002"; - private static final String SERVICE_INSTANCE_NAME = "v3-service-instance"; - private static final String DATE = "2022-01-01T13:35:11Z"; - private static final String DASHBOARD_URL = "dashboard-url"; - private static final String LAST_OPERATION_DESCRIPTION = "Last operation has succeeded"; - private static final String LAST_OPERATION_STATE = "succeeded"; - private static final String LAST_OPERATION_TYPE = "create"; - private static final String MAINTENANCE_INFO_DESCRIPTION = "A long time ago"; - private static final String MAINTENANCE_INFO_VERSION = "0.9.9"; - private static final Map METADATA_ANNOTATIONS = Map.of("foo", "bar"); - private static final Map METADATA_LABELS = Map.of("baz", "mop"); - private static final String SPACE_GUID = "5f5a8db4-342c-11ed-a261-0242ac120002"; - private static final String SERVICE_PLAN_GUID = "810a0322-342c-11ed-a261-0242ac120002"; - private static final String ROUTE_SERVICE_URL = "https://route-service-url"; - private static final String SYSLOG_DRAIN_URL = "https://syslog-drain"; - private static final List TAGS = List.of("one", "two", "three"); - private static final ServiceInstanceType SERVICE_INSTANCE_TYPE = ServiceInstanceType.MANAGED; - private static final boolean UPDATE_AVAILABLE = false; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceInstance(), buildRawServiceInstance()); - } - - private CloudServiceInstance buildExpectedServiceInstance() { - return ImmutableCloudServiceInstance.builder() - .metadata(ImmutableCloudMetadata.builder() - .createdAt(ZonedDateTime.parse(DATE, - DateTimeFormatter.ISO_DATE_TIME) - .toLocalDateTime()) - .updatedAt(ZonedDateTime.parse(DATE, - DateTimeFormatter.ISO_DATE_TIME) - .toLocalDateTime()) - .guid(UUID.fromString(SERVICE_INSTANCE_ID)) - .build()) - .name(SERVICE_INSTANCE_NAME) - .v3Metadata(Metadata.builder() - .annotations(METADATA_ANNOTATIONS) - .labels(METADATA_LABELS) - .build()) - .tags(TAGS) - .build(); - } - - private RawV3CloudServiceInstance buildRawServiceInstance() { - return ImmutableRawV3CloudServiceInstance.builder() - .serviceInstance(buildServiceInstanceResource()) - .build(); - } - - private ServiceInstanceResource buildServiceInstanceResource() { - return ServiceInstanceResource.builder() - .id(SERVICE_INSTANCE_ID) - .name(SERVICE_INSTANCE_NAME) - .createdAt(DATE) - .updatedAt(DATE) - .dashboardUrl(DASHBOARD_URL) - .lastOperation(LastOperation.builder() - .createdAt(DATE) - .description(LAST_OPERATION_DESCRIPTION) - .state(LAST_OPERATION_STATE) - .type(LAST_OPERATION_TYPE) - .updatedAt(DATE) - .build()) - .maintenanceInfo(MaintenanceInfo.builder() - .description(MAINTENANCE_INFO_DESCRIPTION) - .version(MAINTENANCE_INFO_VERSION) - .build()) - .metadata(Metadata.builder() - .annotations(METADATA_ANNOTATIONS) - .labels(METADATA_LABELS) - .build()) - .relationships(ServiceInstanceRelationships.builder() - .space(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SPACE_GUID) - .build()) - .build()) - .servicePlan(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SERVICE_PLAN_GUID) - .build()) - .build()) - .build()) - .routeServiceUrl(ROUTE_SERVICE_URL) - .syslogDrainUrl(SYSLOG_DRAIN_URL) - .tags(TAGS) - .type(SERVICE_INSTANCE_TYPE) - .updateAvailable(UPDATE_AVAILABLE) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java deleted file mode 100644 index e8c49dc920..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - -import org.cloudfoundry.client.v3.LastOperation; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingRelationships; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; - -class RawCloudServiceKeyTest { - - private static final String SERVICE_NAME = "foo"; - private static final String NAME = "bar"; - private static final Map CREDENTIALS = buildTestCredentials(); - private static final CloudServiceInstance SERVICE_INSTANCE = ImmutableCloudServiceInstance.builder() - .name(SERVICE_NAME) - .build(); - private static final LastOperation LAST_OPERATION = buildLastOperation(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceKey(), buildRawServiceKey()); - } - - private static CloudServiceKey buildExpectedServiceKey() { - return ImmutableCloudServiceKey.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .v3Metadata(RawCloudEntityTest.V3_METADATA) - .name(NAME) - .credentials(CREDENTIALS) - .serviceInstance(SERVICE_INSTANCE) - .serviceKeyOperation(ServiceCredentialBindingOperation.from(LAST_OPERATION)) - .build(); - } - - private static RawCloudServiceKey buildRawServiceKey() { - return ImmutableRawCloudServiceKey.builder() - .serviceBindingResource(buildTestResource()) - .serviceInstance(SERVICE_INSTANCE) - .credentials(CREDENTIALS) - .build(); - } - - private static ServiceBindingResource buildTestResource() { - return ServiceBindingResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .metadata(RawCloudEntityTest.V3_METADATA) - .name(NAME) - .type(ServiceBindingType.KEY) - .lastOperation(LAST_OPERATION) - .relationships(ServiceBindingRelationships.builder() - .serviceInstance(RawCloudEntityTest.buildToOneRelationship("")) - .build()) - .build(); - } - - private static Map buildTestCredentials() { - Map parameters = new HashMap<>(); - parameters.put("foo", "bar"); - parameters.put("baz", false); - parameters.put("qux", 3.141); - return parameters; - } - - private static LastOperation buildLastOperation() { - return LastOperation.builder() - .state(ServiceCredentialBindingOperation.State.SUCCEEDED.toString()) - .type(ServiceCredentialBindingOperation.Type.CREATE.toString()) - .createdAt(LocalDateTime.now() - .toString()) - .updatedAt(LocalDateTime.now() - .toString()) - .description("Service key created") - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOfferingTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOfferingTest.java deleted file mode 100644 index b80fd6d986..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOfferingTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.serviceofferings.BrokerCatalog; -import org.cloudfoundry.client.v3.serviceofferings.Features; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingRelationships; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; - -public class RawCloudServiceOfferingTest { - - private static final String NAME = "postgresql"; - static final boolean AVAILABLE = true; - static final boolean BINDABLE = true; - static final boolean ALLOW_CONTEXT_UPDATES = false; - static final boolean BINDINGS_RETRIEVABLE = true; - static final boolean INSTANCES_RETRIEVABLE = true; - static final boolean PLAN_UPDATEABLE = false; - static final boolean SHAREABLE = true; - static final String DESCRIPTION = "description"; - static final String DOCUMENTATION_URL = "/documentation"; - static final String UNIQUE_ID = "unique-id"; - static final String SERVICE_BROKER_GUID = UUID.randomUUID() - .toString(); - private static final Map EXTRA = Map.of("key-metadata", "value-metadata"); - private static final List PLANS = buildTestServiceBindings(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceOffering(), buildRawServiceOffering()); - } - - private static CloudServiceOffering buildExpectedServiceOffering() { - return ImmutableCloudServiceOffering.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .brokerId(SERVICE_BROKER_GUID) - .name(NAME) - .isAvailable(AVAILABLE) - .isBindable(BINDABLE) - .isShareable(SHAREABLE) - .description(DESCRIPTION) - .extra(EXTRA) - .docUrl(DOCUMENTATION_URL) - .uniqueId(UNIQUE_ID) - .servicePlans(PLANS) - .build(); - } - - private static RawCloudServiceOffering buildRawServiceOffering() { - return ImmutableRawCloudServiceOffering.builder() - .serviceOffering(buildTestServiceOffering()) - .servicePlans(PLANS) - .build(); - } - - public static ServiceOfferingResource buildTestServiceOffering() { - return ServiceOfferingResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .available(AVAILABLE) - .name(NAME) - .brokerCatalog(BrokerCatalog.builder() - .brokerCatalogId(UNIQUE_ID) - .features(Features.builder() - .bindable(BINDABLE) - .allowContextUpdates(ALLOW_CONTEXT_UPDATES) - .bindingsRetrievable(BINDINGS_RETRIEVABLE) - .instancesRetrievable(INSTANCES_RETRIEVABLE) - .planUpdateable(PLAN_UPDATEABLE) - .build()) - .metadata(EXTRA) - .build()) - .relationships(ServiceOfferingRelationships.builder() - .serviceBroker(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SERVICE_BROKER_GUID) - .build()) - .build()) - .build()) - .shareable(SHAREABLE) - .description(DESCRIPTION) - .documentationUrl(DOCUMENTATION_URL) - .build(); - } - - private static List buildTestServiceBindings() { - return List.of(buildTestServiceBinding()); - } - - private static CloudServicePlan buildTestServiceBinding() { - return ImmutableCloudServicePlan.builder() - .name("v9.4-small") - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlanTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlanTest.java deleted file mode 100644 index f43f5e6a6a..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlanTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Map; -import java.util.UUID; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.serviceplans.BrokerCatalog; -import org.cloudfoundry.client.v3.serviceplans.Features; -import org.cloudfoundry.client.v3.serviceplans.Schema; -import org.cloudfoundry.client.v3.serviceplans.Schemas; -import org.cloudfoundry.client.v3.serviceplans.ServiceInstanceSchema; -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.cloudfoundry.client.v3.serviceplans.ServicePlanRelationships; -import org.cloudfoundry.client.v3.serviceplans.ServicePlanResource; -import org.cloudfoundry.client.v3.serviceplans.Visibility; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; - -public class RawCloudServicePlanTest { - - private static final String NAME = "v9.4-small"; - private static final String DESCRIPTION = "description"; - private static final Map EXTRA = Map.of("extra", "value"); - private static final String UNIQUE_ID = "unique-id"; - private static final boolean PUBLIC = true; - private static final boolean FREE = false; - private static final boolean PLAN_BINDABLE = true; - private static final boolean PLAN_UPDATABLE = true; - private static final boolean AVAILABLE = true; - private static final String SERVICE_OFFERING_ID = UUID.randomUUID() - .toString(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedPlan(), buildRawServicePlan()); - } - - private static CloudServicePlan buildExpectedPlan() { - return ImmutableCloudServicePlan.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .description(DESCRIPTION) - .extra(EXTRA) - .uniqueId(UNIQUE_ID) - .isPublic(PUBLIC) - .isFree(FREE) - .serviceOfferingId(SERVICE_OFFERING_ID) - .build(); - } - - private static RawCloudServicePlan buildRawServicePlan() { - return ImmutableRawCloudServicePlan.of(buildTestServicePlan(NAME)); - } - - public static ServicePlan buildTestServicePlan(String planName) { - return ServicePlanResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .available(AVAILABLE) - .name(planName) - .description(DESCRIPTION) - .brokerCatalog(BrokerCatalog.builder() - .metadata(EXTRA) - .brokerCatalogId(UNIQUE_ID) - .features(Features.builder() - .bindable(PLAN_BINDABLE) - .planUpdateable(PLAN_UPDATABLE) - .build()) - .build()) - .visibilityType(Visibility.PUBLIC) - .free(FREE) - .relationships(ServicePlanRelationships.builder() - .serviceOffering(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SERVICE_OFFERING_ID) - .build()) - .build()) - .build()) - .schemas(Schemas.builder() - .serviceInstance(ServiceInstanceSchema.builder() - .create(Schema.builder() - .build()) - .build()) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java deleted file mode 100644 index 8a4fec8ef0..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Arrays; -import java.util.List; - -import org.cloudfoundry.client.v3.LastOperation; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceResource; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; -import org.cloudfoundry.client.v3.serviceofferings.BrokerCatalog; -import org.cloudfoundry.client.v3.serviceofferings.Features; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingRelationships; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation.State; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation.Type; - -class RawCloudServiceTest { - - private static final String NAME = "my-db"; - private static final String OFFERING_NAME = "postgresql"; - private static final String PLAN_NAME = "v9.4-small"; - private static final List TAGS = Arrays.asList("test-tag-1", "test-tag-2"); - private static final String LAST_OPERATION_TYPE_CREATE = "create"; - private static final String LAST_OPERATION_STATE_IN_PROGRESS = "in progress"; - private static final String LAST_OPERATION_STATE_SUCCEEDED = "succeeded"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedService(), buildRawService()); - } - - @Test - void testDeriveWithUserProvidedService() { - RawCloudEntityTest.testDerive(buildExpectedUserProvidedService(), buildRawUserProvidedService()); - } - - private static CloudServiceInstance buildExpectedService() { - return ImmutableCloudServiceInstance.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .plan(PLAN_NAME) - .label(OFFERING_NAME) - .type(ServiceInstanceType.MANAGED) - .tags(TAGS) - .lastOperation(new ServiceOperation(Type.CREATE, "", State.IN_PROGRESS)) - .build(); - } - - private static CloudServiceInstance buildExpectedUserProvidedService() { - return ImmutableCloudServiceInstance.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .type(ServiceInstanceType.USER_PROVIDED) - .tags(TAGS) - .lastOperation(new ServiceOperation(Type.CREATE, "", State.SUCCEEDED)) - .build(); - } - - private static RawCloudServiceInstance buildRawService() { - return ImmutableRawCloudServiceInstance.builder() - .resource(buildTestResource(false, LastOperation.builder() - .type(LAST_OPERATION_TYPE_CREATE) - .state(LAST_OPERATION_STATE_IN_PROGRESS) - .build())) - .servicePlan(RawCloudServicePlanTest.buildTestServicePlan(PLAN_NAME)) - .serviceOffering(buildTestServiceOffering()) - .build(); - } - - private static RawCloudServiceInstance buildRawUserProvidedService() { - return ImmutableRawCloudServiceInstance.builder() - .resource(buildTestResource(true, LastOperation.builder() - .type(LAST_OPERATION_TYPE_CREATE) - .state(LAST_OPERATION_STATE_SUCCEEDED) - .build())) - .build(); - } - - private static ServiceInstanceResource buildTestResource(boolean isUserProvided, LastOperation lastOperation) { - ServiceInstanceResource.Builder serviceInstanceResourceBuilder = ServiceInstanceResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .name(NAME) - .type(ServiceInstanceType.MANAGED) - .addAllTags(TAGS) - .lastOperation(lastOperation); - if (isUserProvided) { - serviceInstanceResourceBuilder.type(ServiceInstanceType.USER_PROVIDED); - } - return serviceInstanceResourceBuilder.build(); - } - - private static ServiceOfferingResource buildTestServiceOffering() { - return ServiceOfferingResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .available(RawCloudServiceOfferingTest.AVAILABLE) - .name(OFFERING_NAME) - .brokerCatalog(BrokerCatalog.builder() - .brokerCatalogId(RawCloudServiceOfferingTest.UNIQUE_ID) - .features(Features.builder() - .bindable(RawCloudServiceOfferingTest.BINDABLE) - .allowContextUpdates(RawCloudServiceOfferingTest.ALLOW_CONTEXT_UPDATES) - .bindingsRetrievable(RawCloudServiceOfferingTest.BINDINGS_RETRIEVABLE) - .instancesRetrievable(RawCloudServiceOfferingTest.INSTANCES_RETRIEVABLE) - .planUpdateable(RawCloudServiceOfferingTest.PLAN_UPDATEABLE) - .build()) - .build()) - .relationships(ServiceOfferingRelationships.builder() - .serviceBroker(ToOneRelationship.builder() - .data(Relationship.builder() - .id(RawCloudServiceOfferingTest.SERVICE_BROKER_GUID) - .build()) - .build()) - .build()) - .shareable(RawCloudServiceOfferingTest.SHAREABLE) - .description(RawCloudServiceOfferingTest.DESCRIPTION) - .documentationUrl(RawCloudServiceOfferingTest.DOCUMENTATION_URL) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStackTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStackTest.java deleted file mode 100644 index ac4da5f8b2..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStackTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.stacks.Stack; -import org.cloudfoundry.client.v3.stacks.StackResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudStack; - -class RawCloudStackTest { - - private static final String NAME = "cflinuxfs3"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedStack(), buildRawStack()); - } - - private static CloudStack buildExpectedStack() { - return ImmutableCloudStack.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .name(NAME) - .build(); - } - - private static RawCloudStack buildRawStack() { - return ImmutableRawCloudStack.of(buildTestResource()); - } - - private static Stack buildTestResource() { - return StackResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .metadata(RawCloudEntityTest.V3_METADATA) - .name(NAME) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTaskTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTaskTest.java deleted file mode 100644 index 9d69a9da4c..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTaskTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.tasks.Result; -import org.cloudfoundry.client.v3.tasks.Task; -import org.cloudfoundry.client.v3.tasks.TaskResource; -import org.cloudfoundry.client.v3.tasks.TaskState; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; - -class RawCloudTaskTest { - - private static final String NAME = "echo"; - private static final String COMMAND = "echo \"Hello!\""; - private static final int DISK_IN_MB = 128; - private static final int MEMORY_IN_MB = 256; - private static final String FAILURE_REASON = "blabla"; - private static final String DROPLET_ID = "8ff24465-823b-4a55-85ee-a680f2c743cd"; - private static final int SEQUENCE_ID = 3; - private static final TaskState STATE = TaskState.FAILED; - - private static final CloudTask.State EXPECTED_STATE = CloudTask.State.FAILED; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedTask(), buildRawTask()); - } - - private static CloudTask buildExpectedTask() { - return ImmutableCloudTask.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .command(COMMAND) - .limits(ImmutableCloudTask.ImmutableLimits.builder() - .disk(DISK_IN_MB) - .memory(MEMORY_IN_MB) - .build()) - .result(ImmutableCloudTask.ImmutableResult.builder() - .failureReason(FAILURE_REASON) - .build()) - .state(EXPECTED_STATE) - .build(); - } - - private static RawCloudTask buildRawTask() { - return ImmutableRawCloudTask.of(buildTestResource()); - } - - private static Task buildTestResource() { - return TaskResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .name(NAME) - .command(COMMAND) - .diskInMb(DISK_IN_MB) - .memoryInMb(MEMORY_IN_MB) - .state(STATE) - .result(buildTestResult()) - .dropletId(DROPLET_ID) - .sequenceId(SEQUENCE_ID) - .build(); - } - - private static Result buildTestResult() { - return Result.builder() - .failureReason(FAILURE_REASON) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfoTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfoTest.java deleted file mode 100644 index 2051f7a207..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfoTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.applications.GetApplicationProcessStatisticsResponse; -import org.cloudfoundry.client.v3.processes.ProcessState; -import org.cloudfoundry.client.v3.processes.ProcessStatisticsResource; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstanceInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; -import org.junit.jupiter.api.Test; - -class RawInstancesInfoTest { - - @Test - void testDeriveWithTrueRoutable() { - RawCloudEntityTest.testDerive(buildExpectedInstancesInfo(true), buildActualInstancesInfo("true")); - } - - @Test - void testDeriveWithFalseRoutable() { - RawCloudEntityTest.testDerive(buildExpectedInstancesInfo(false), buildActualInstancesInfo("false")); - } - - private InstancesInfo buildExpectedInstancesInfo(boolean expectedRoutable) { - return ImmutableInstancesInfo.builder() - .addInstance(ImmutableInstanceInfo.builder() - .index(0) - .state(InstanceState.RUNNING) - .isRoutable(expectedRoutable) - .build()) - .build(); - } - - private RawInstancesInfo buildActualInstancesInfo(String routable) { - return ImmutableRawInstancesInfo.builder() - .processStatisticsResponse(getApplicationProcessStatisticsResponse(routable)) - .build(); - } - - private GetApplicationProcessStatisticsResponse getApplicationProcessStatisticsResponse(String routable) { - return GetApplicationProcessStatisticsResponse.builder() - .resource(ProcessStatisticsResource.builder() - .index(0) - .details("Instance is in running state") - .diskQuota(1024L) - .state(ProcessState.RUNNING) - .memoryQuota(1024L) - .type("web") - .uptime(9042L) - .fileDescriptorQuota(1024L) - .routable(routable) - .host("10.244.16.10") - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRoleTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRoleTest.java deleted file mode 100644 index bc650d4603..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRoleTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.roles.RoleRelationships; -import org.cloudfoundry.client.v3.roles.RoleResource; -import org.cloudfoundry.client.v3.roles.RoleType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; - -class RawUserRoleTest { - - private static final String USER_ROLE_GUID = "c94b4588-3431-11ed-a261-0242ac120002"; - private static final String USER_GUID = "328eaf4e-3432-11ed-a261-0242ac120002"; - private static final String DATE = "2022-01-01T13:35:11Z"; - private static final RoleType USER_ROLE_TYPE = RoleType.SPACE_DEVELOPER; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedUserRole(), buildActualUserRole()); - } - - private UserRole buildExpectedUserRole() { - return UserRole.SPACE_DEVELOPER; - } - - private RawUserRole buildActualUserRole() { - return ImmutableRawUserRole.builder() - .roleResource(buildRoleResource()) - .build(); - } - - private RoleResource buildRoleResource() { - return RoleResource.builder() - .createdAt(DATE) - .id(USER_ROLE_GUID) - .type(USER_ROLE_TYPE) - .relationships(RoleRelationships.builder() - .user(ToOneRelationship.builder() - .data(Relationship.builder() - .id(USER_GUID) - .build()) - .build()) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java new file mode 100644 index 0000000000..b1774404fd --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java @@ -0,0 +1,496 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ImmutableApplicationToCreateDto; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import static org.mockito.Mockito.when; + +class ApplicationsV3OperationsTest { + + private static final String SPACE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String APPS_BASE = MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + + private ApplicationsV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ApplicationsV3Operations(factory.client(), target); + } + + @Test + void testCreateApplicationPostsAndReturnsGuid() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "my-app", "STOPPED"), MediaType.APPLICATION_JSON)); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .build(); + + UUID result = operations.createApplication(dto); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result); + factory.verify(); + } + + @Test + void testCreateApplicationWithScaleAlsoScalesWebProcess() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "my-app", "STOPPED"), MediaType.APPLICATION_JSON)); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web/actions/scale", HttpMethod.POST); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .memoryInMb(512) + .diskQuotaInMb(1024) + .staging(ImmutableStaging.builder() + .addBuildpacks("java_buildpack") + .stackName("cflinuxfs4") + .build()) + .env(Map.of("FOO", "bar")) + .metadata(ImmutableMetadata.builder() + .putLabel("k", "v") + .build()) + .build(); + + UUID result = operations.createApplication(dto); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result); + factory.verify(); + } + + @Test + void testCreateApplicationThrowsWhenTargetGuidIsNull() { + when(target.getGuid()) + .thenReturn(null); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.createApplication(dto)); + } + + @Test + void testDeleteApplicationLooksUpGuidAndDeletes() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.DELETE); + + operations.deleteApplication("my-app"); + + factory.verify(); + } + + @Test + void testDeleteApplicationThrowsWhenApplicationNotFound() { + when(target.getGuid()) + .thenReturn(null); + + stubEmptyAppLookupByName("missing-app"); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.deleteApplication("missing-app")); + } + + @Test + void testGetApplicationReturnsMappedApplication() { + when(target.getGuid()) + .thenReturn(null); + + factory.stubGet(APPS_BASE + "&names=my-app", listJson(appJson(APP_GUID, "my-app", "STARTED"))); + + CloudApplication result = operations.getApplication("my-app"); + + Assertions.assertEquals("my-app", result.getName()); + Assertions.assertEquals(CloudApplication.State.STARTED, result.getState()); + Assertions.assertEquals(UUID.fromString(APP_GUID), result.getGuid()); + factory.verify(); + } + + @Test + void testGetApplicationThrowsWhenRequiredAndNotFound() { + when(target.getGuid()) + .thenReturn(null); + + stubEmptyAppLookupByName("missing-app"); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.getApplication("missing-app")); + } + + @Test + void testGetApplicationReturnsNullWhenNotRequiredAndNotFound() { + when(target.getGuid()) + .thenReturn(null); + + stubEmptyAppLookupByName("missing-app"); + + CloudApplication result = operations.getApplication("missing-app", false); + + Assertions.assertNull(result); + factory.verify(); + } + + @Test + void testGetApplicationGuidReturnsGuid() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + UUID result = operations.getApplicationGuid("my-app"); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result); + factory.verify(); + } + + @Test + void testGetApplicationQueryIncludesSpaceGuidWhenPresent() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + factory.stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", listJson(appJson(APP_GUID, "my-app", "STARTED"))); + + CloudApplication result = operations.getApplication("my-app"); + + Assertions.assertEquals("my-app", result.getName()); + factory.verify(); + } + + @Test + void testGetApplicationNameReturnsName() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, appJson(APP_GUID, "my-app", "STARTED")); + + String result = operations.getApplicationName(UUID.fromString(APP_GUID)); + + Assertions.assertEquals("my-app", result); + factory.verify(); + } + + @Test + void testGetApplicationEnvironmentByGuidReturnsVariables() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", "{\"var\":{\"FOO\":\"bar\"}}"); + + Map result = operations.getApplicationEnvironment(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(Map.of("FOO", "bar"), result); + factory.verify(); + } + + @Test + void testGetApplicationEnvironmentByGuidReturnsEmptyWhenNoVars() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", "{}"); + + Map result = operations.getApplicationEnvironment(UUID.fromString(APP_GUID)); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetApplicationEnvironmentByNameResolvesGuidFirst() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", "{\"var\":{\"A\":\"B\"}}"); + + Map result = operations.getApplicationEnvironment("my-app"); + + Assertions.assertEquals(Map.of("A", "B"), result); + factory.verify(); + } + + @Test + void testGetApplicationsReturnsMappedList() { + when(target.getGuid()) + .thenReturn(null); + + factory.stubGet(APPS_BASE, listJson(appJson(APP_GUID, "my-app", "STARTED"))); + + List result = operations.getApplications(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("my-app", result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetApplicationsReturnsEmptyList() { + when(target.getGuid()) + .thenReturn(null); + + factory.stubGet(APPS_BASE, "{\"resources\":[]}"); + + List result = operations.getApplications(); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetApplicationsByMetadataLabelSelectorAppendsSelector() { + when(target.getGuid()) + .thenReturn(null); + + factory.stubGet(APPS_BASE + "&label_selector=env", listJson(appJson(APP_GUID, "my-app", "STARTED"))); + + List result = operations.getApplicationsByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetApplicationsByMetadataLabelSelectorWithNullSelector() { + when(target.getGuid()) + .thenReturn(null); + + factory.stubGet(APPS_BASE, "{\"resources\":[]}"); + + List result = operations.getApplicationsByMetadataLabelSelector(null); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testStartApplicationPostsToStartAction() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/actions/start", HttpMethod.POST); + + operations.startApplication("my-app"); + + factory.verify(); + } + + @Test + void testStopApplicationPostsToStopAction() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/actions/stop", HttpMethod.POST); + + operations.stopApplication("my-app"); + + factory.verify(); + } + + @Test + void testRenameUpdatesName() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); + + operations.rename("my-app", "new-name"); + + factory.verify(); + } + + @Test + void testRenameSwallowsServiceUnavailableWhenAlreadyRenamed() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.SERVICE_UNAVAILABLE)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, appJson(APP_GUID, "new-name", "STOPPED")); + + operations.rename("my-app", "new-name"); + + factory.verify(); + } + + @Test + void testRenameRethrowsServiceUnavailableWhenNotRenamed() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.SERVICE_UNAVAILABLE)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, appJson(APP_GUID, "still-old", "STOPPED")); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.rename("my-app", "new-name")); + } + + @Test + void testUpdateApplicationInstancesScalesWebProcess() { + stubScaleFlow(); + + operations.updateApplicationInstances("my-app", 3); + + factory.verify(); + } + + @Test + void testUpdateApplicationMemoryScalesWebProcess() { + stubScaleFlow(); + + operations.updateApplicationMemory("my-app", 512); + + factory.verify(); + } + + @Test + void testUpdateApplicationDiskQuotaScalesWebProcess() { + stubScaleFlow(); + + operations.updateApplicationDiskQuota("my-app", 2048); + + factory.verify(); + } + + @Test + void testUpdateApplicationEnvUpdatesEnvVars() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", HttpMethod.PATCH); + + operations.updateApplicationEnv("my-app", Map.of("FOO", "bar")); + + factory.verify(); + } + + @Test + void testBindDropletToAppUpdatesCurrentDroplet() { + UUID dropletGuid = UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/relationships/current_droplet", HttpMethod.PATCH); + + operations.bindDropletToApp(dropletGuid, UUID.fromString(APP_GUID)); + + factory.verify(); + } + + @Test + void testUpdateApplicationMetadataUpdatesApp() { + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); + + Metadata metadata = ImmutableMetadata.builder() + .putLabel("k", "v") + .putAnnotation("a", "b") + .build(); + + operations.updateApplicationMetadata(UUID.fromString(APP_GUID), metadata); + + factory.verify(); + } + + @Test + void testUpdateApplicationMetadataSendsNullLabelValueToDeleteLabel() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andExpect(MockRestRequestMatchers.content() + .string(org.hamcrest.Matchers.containsString("\"obsolete\":null"))) + .andRespond(MockRestResponseCreators.withSuccess()); + + Metadata metadata = ImmutableMetadata.builder() + .putLabel("obsolete", null) + .build(); + + operations.updateApplicationMetadata(UUID.fromString(APP_GUID), metadata); + + factory.verify(); + } + + private void stubScaleFlow() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web/actions/scale", HttpMethod.POST); + } + + private void stubAppLookupByName(String name, String guid) { + factory.stubGet(APPS_BASE + "&names=" + name, listJson(appJson(guid, name, "STARTED"))); + } + + private void stubEmptyAppLookupByName(String name) { + factory.stubGet(APPS_BASE + "&names=" + name, "{\"resources\":[]}"); + } + + private static String appJson(String guid, String name, String state) { + return "{\"guid\":\"" + guid + "\",\"name\":\"" + name + "\",\"state\":\"" + state + + "\",\"lifecycle\":{\"type\":\"buildpack\",\"data\":{\"buildpacks\":[\"java_buildpack\"],\"stack\":\"cflinuxfs4\"}}}"; + } + + private static String listJson(String... resources) { + return "{\"resources\":[" + String.join(",", resources) + "]}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java new file mode 100644 index 0000000000..2838eb3e1a --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java @@ -0,0 +1,128 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class BuildsV3OperationsTest { + + private static final String BUILD_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String PACKAGE_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String DROPLET_GUID = "99999999-8888-7777-6666-555555555555"; + + private MockControllerClientFactory factory; + private BuildsV3Operations operations; + + @BeforeEach + void setUp() { + factory = MockControllerClientFactory.create(); + operations = new BuildsV3Operations(factory.client()); + } + + @Test + void testCreateBuildPostsPackageAndReturnsMappedBuild() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getBuildJson("STAGING"), MediaType.APPLICATION_JSON)); + + CloudBuild result = operations.createBuild(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(CloudBuild.State.STAGING, result.getState()); + Assertions.assertEquals(UUID.fromString(BUILD_GUID), result.getMetadata() + .getGuid()); + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getPackageInfo() + .getGuid()); + factory.verify(); + } + + @Test + void testGetBuildReturnsMappedBuild() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID, getBuildJson("STAGED")); + + CloudBuild result = operations.getBuild(UUID.fromString(BUILD_GUID)); + + Assertions.assertEquals(CloudBuild.State.STAGED, result.getState()); + Assertions.assertEquals(UUID.fromString(DROPLET_GUID), result.getDropletInfo() + .getGuid()); + factory.verify(); + } + + @Test + void testGetBuildThrowsWhenNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + UUID buildGuid = UUID.fromString(BUILD_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> operations.getBuild(buildGuid)); + } + + @Test + void testGetBuildsForApplicationReturnsMappedList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/builds?per_page=50", "{\"resources\":[" + getBuildJson("STAGED") + "]}"); + + List result = operations.getBuildsForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(CloudBuild.State.STAGED, result.getFirst() + .getState()); + factory.verify(); + } + + @Test + void testGetBuildsForApplicationReturnsEmptyList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/builds?per_page=50", "{\"resources\":[]}"); + + List result = operations.getBuildsForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + @Test + void testGetBuildsForPackageReturnsMappedList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + + PACKAGE_GUID + "&per_page=50", "{\"resources\":[" + getBuildJson("FAILED") + "]}"); + + List result = operations.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(CloudBuild.State.FAILED, result.getFirst() + .getState()); + Assertions.assertEquals("staging failed", result.getFirst() + .getError()); + factory.verify(); + } + + @Test + void testGetBuildsForPackageReturnsEmptyList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + + PACKAGE_GUID + "&per_page=50", "{\"resources\":[]}"); + + List result = operations.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + private static String getBuildJson(String state) { + return "{\"guid\":\"" + BUILD_GUID + "\",\"state\":\"" + state + "\",\"error\":\"staging failed\",\"package\":{\"guid\":\"" + + PACKAGE_GUID + "\"},\"droplet\":{\"guid\":\"" + DROPLET_GUID + "\"}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImplTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImplTest.java deleted file mode 100644 index 0519cbecce..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImplTest.java +++ /dev/null @@ -1,309 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.rest; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import java.util.stream.Stream; - -import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.ClientV3Exception; -import org.cloudfoundry.client.v3.Lifecycle; -import org.cloudfoundry.client.v3.LifecycleType; -import org.cloudfoundry.client.v3.Pagination; -import org.cloudfoundry.client.v3.applications.ApplicationResource; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.cloudfoundry.client.v3.applications.ApplicationsV3; -import org.cloudfoundry.client.v3.applications.GetApplicationRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationResponse; -import org.cloudfoundry.client.v3.applications.ListApplicationsRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationsResponse; -import org.cloudfoundry.client.v3.applications.UpdateApplicationRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationResponse; -import org.cloudfoundry.client.v3.serviceofferings.GetServiceOfferingRequest; -import org.cloudfoundry.client.v3.serviceofferings.GetServiceOfferingResponse; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOffering; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingsV3; -import org.cloudfoundry.client.v3.serviceplans.GetServicePlanRequest; -import org.cloudfoundry.client.v3.serviceplans.GetServicePlanResponse; -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.cloudfoundry.client.v3.serviceplans.ServicePlansV3; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.springframework.http.HttpStatus; - -import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.RawCloudServiceOfferingTest; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.RawCloudServicePlanTest; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudSpace; - -import reactor.core.publisher.Mono; - -class CloudControllerRestClientImplTest { - - private static final String SERVICE_PLAN_GUID = "1803e5a7-40c7-438e-b2be-e2045c9b7cda"; - private static final String SERVICE_INSTANCE_GUID = "26949ebb-a624-35c0-000-1110a01f1880"; - private static final String SERVICE_OFFERING_GUID = "1803e5a7-40c7-438e-b2be-e2045c9b7cda"; - private static final String PLAN_NAME = "test-plan"; - private static final String OLD_APPLICATION_NAME = "old-app-name"; - private static final String NEW_APPLICATION_NAME = "new-app-name"; - private static final String APPLICATION_CREATED_AT = "2024-01-26T10:00:00"; - private static final UUID APPLICATION_GUID = UUID.randomUUID(); - private static final UUID SPACE_GUID = UUID.randomUUID(); - - @Mock - private CloudFoundryClient delegate; - private CloudControllerRestClientImpl controllerClient; - - @BeforeEach - public void setUp() throws Exception { - MockitoAnnotations.openMocks(this) - .close(); - controllerClient = new CloudControllerRestClientImpl(delegate); - } - - @Test - void testGetServiceOffering() { - GetServiceOfferingRequest request = GetServiceOfferingRequest.builder() - .serviceOfferingId(SERVICE_PLAN_GUID) - .build(); - GetServiceOfferingResponse response = GetServiceOfferingResponse.builder() - .from(RawCloudServiceOfferingTest.buildTestServiceOffering()) - .build(); - - ServiceOfferingsV3 serviceOfferingsV3 = Mockito.mock(ServiceOfferingsV3.class); - Mockito.when(delegate.serviceOfferingsV3()) - .thenReturn(serviceOfferingsV3); - Mockito.when(serviceOfferingsV3.get(request)) - .thenReturn(Mono.just(response)); - - ServiceOffering serviceOffering = controllerClient.getServiceOffering(SERVICE_PLAN_GUID) - .block(); - - assertEquals(response, serviceOffering); - } - - public static Stream testGetServiceOfferingWithError() { - return Stream.of( -// @formatter:off - Arguments.of(HttpStatus.FORBIDDEN.value(), - "403 Forbidden: Service offering with guid \"1803e5a7-40c7-438e-b2be-e2045c9b7cda\" is not available."), - Arguments.of(HttpStatus.NOT_FOUND.value(), - "404 Not Found: Service offering with guid \"1803e5a7-40c7-438e-b2be-e2045c9b7cda\" not found.") -// @formatter:on - ); - } - - @ParameterizedTest - @MethodSource - void testGetServiceOfferingWithError(int errorCode, String expectedErrorMessage) { - GetServiceOfferingRequest request = GetServiceOfferingRequest.builder() - .serviceOfferingId(SERVICE_OFFERING_GUID) - .build(); - ServiceOfferingsV3 serviceOfferingsV3 = Mockito.mock(ServiceOfferingsV3.class); - Mockito.when(delegate.serviceOfferingsV3()) - .thenReturn(serviceOfferingsV3); - Mockito.when(serviceOfferingsV3.get(request)) - .thenReturn(Mono.error(clientV3Exception(errorCode))); - Exception cloudControllerException = assertThrows(CloudOperationException.class, - () -> controllerClient.getServiceOffering(SERVICE_OFFERING_GUID) - .block()); - assertEquals(expectedErrorMessage, cloudControllerException.getMessage()); - } - - public static Stream testGetServicePlanWithError() { - return Stream.of( -// @formatter:off - Arguments.of(HttpStatus.FORBIDDEN.value(), - "403 Forbidden: Service plan with guid \"1803e5a7-40c7-438e-b2be-e2045c9b7cda\" is not available for service instance \"26949ebb-a624-35c0-000-1110a01f1880\"."), - Arguments.of(HttpStatus.NOT_FOUND.value(), - "404 Not Found: Service plan with guid \"1803e5a7-40c7-438e-b2be-e2045c9b7cda\" for service instance with name \"26949ebb-a624-35c0-000-1110a01f1880\" was not found.") -// @formatter:on - ); - } - - @ParameterizedTest - @MethodSource - void testGetServicePlanWithError(int errorCode, String expectedErrorMessage) { - GetServicePlanRequest request = GetServicePlanRequest.builder() - .servicePlanId(SERVICE_PLAN_GUID) - .build(); - - ServicePlansV3 servicePlans = Mockito.mock(ServicePlansV3.class); - Mockito.when(delegate.servicePlansV3()) - .thenReturn(servicePlans); - Mockito.when(servicePlans.get(request)) - .thenReturn(Mono.error(clientV3Exception(errorCode))); - - Exception cloudControllerException = assertThrows(CloudOperationException.class, - () -> controllerClient.getServicePlanResource(SERVICE_PLAN_GUID, - SERVICE_INSTANCE_GUID) - .block()); - assertEquals(expectedErrorMessage, cloudControllerException.getMessage()); - } - - @Test - void testGetServicePlanResource() { - GetServicePlanRequest request = GetServicePlanRequest.builder() - .servicePlanId(SERVICE_PLAN_GUID) - .build(); - GetServicePlanResponse response = GetServicePlanResponse.builder() - .from(RawCloudServicePlanTest.buildTestServicePlan(PLAN_NAME)) - .build(); - - ServicePlansV3 servicePlans = Mockito.mock(ServicePlansV3.class); - Mockito.when(delegate.servicePlansV3()) - .thenReturn(servicePlans); - Mockito.when(servicePlans.get(request)) - .thenReturn(Mono.just(response)); - - ServicePlan servicePlanResource = controllerClient.getServicePlanResource(SERVICE_PLAN_GUID, SERVICE_INSTANCE_GUID) - .block(); - - assertEquals(response, servicePlanResource); - } - - private static Stream testRenameApplication() { - return Stream.of( - // (1) Successful application rename - Arguments.of(Mono.just(UpdateApplicationResponse.builder() - .name(NEW_APPLICATION_NAME) - .id(APPLICATION_GUID.toString()) - .createdAt(APPLICATION_CREATED_AT) - .state(ApplicationState.STARTED) - .lifecycle(getApplicationLifecycle()) - .build()), - null, false), - // (2) Controller's API returns 503 and rename was actually done - Arguments.of(Mono.error(new ClientV3Exception(503, - List.of(org.cloudfoundry.client.v3.Error.builder() - .code(170015) - .title("CF-RunnerUnavailable") - .detail("Runner is unavailable") - .build()))), - Mono.just(GetApplicationResponse.builder() - .name(NEW_APPLICATION_NAME) - .id(APPLICATION_GUID.toString()) - .createdAt(APPLICATION_CREATED_AT) - .state(ApplicationState.STARTED) - .lifecycle(getApplicationLifecycle()) - .build()), - false), - // (3) Controller's API fails with 500 and application wasn't renamed - Arguments.of(Mono.error(new ClientV3Exception(500, Collections.emptyList())), null, true), - // (4) Controller's API returns 503 and while make request to get current application name the request fails - Arguments.of(Mono.error(new ClientV3Exception(503, - List.of(org.cloudfoundry.client.v3.Error.builder() - .code(170015) - .title("CF-RunnerUnavailable") - .detail("Runner is unavailable") - .build()))), - Mono.error(new ClientV3Exception(500, Collections.emptyList())), true), - // (5) Controller's API returns 503 and application wasn't renamed - Arguments.of(Mono.error(new ClientV3Exception(503, - List.of(org.cloudfoundry.client.v3.Error.builder() - .code(170015) - .title("CF-RunnerUnavailable") - .detail("Runner is unavailable") - .build()))), - Mono.just(GetApplicationResponse.builder() - .name(OLD_APPLICATION_NAME) - .id(APPLICATION_GUID.toString()) - .createdAt(APPLICATION_CREATED_AT) - .state(ApplicationState.STARTED) - .lifecycle(getApplicationLifecycle()) - .build()), - true)); - } - - private static Lifecycle getApplicationLifecycle() { - return Lifecycle.builder() - .type(LifecycleType.BUILDPACK) - .data(BuildpackData.builder() - .build()) - .build(); - } - - @ParameterizedTest - @MethodSource - void testRenameApplication(Mono updateApplicationResponse, - Mono getApplicationResponse, boolean expectedFailure) { - ApplicationsV3 applicationsV3 = Mockito.mock(ApplicationsV3.class); - var updateAppRequest = UpdateApplicationRequest.builder() - .applicationId(APPLICATION_GUID.toString()) - .name(NEW_APPLICATION_NAME) - .build(); - prepareClientDelegate(applicationsV3, updateAppRequest, updateApplicationResponse, getApplicationResponse); - - if (expectedFailure) { - assertThrows(ClientV3Exception.class, () -> controllerClient.rename(OLD_APPLICATION_NAME, NEW_APPLICATION_NAME)); - return; - } - controllerClient.rename(OLD_APPLICATION_NAME, NEW_APPLICATION_NAME); - - Mockito.verify(applicationsV3, Mockito.atMostOnce()) - .update(updateAppRequest); - } - - private void prepareClientDelegate(ApplicationsV3 applicationsV3, UpdateApplicationRequest updateAppRequest, - Mono updateApplicationResponse, - Mono getApplicationResponse) { - initControllerClientWithTargetSpace(SPACE_GUID); - - Mockito.when(delegate.applicationsV3()) - .thenReturn(applicationsV3); - var listAppsRequest = ListApplicationsRequest.builder() - .spaceId(SPACE_GUID.toString()) - .name(OLD_APPLICATION_NAME) - .page(1) - .build(); - - var listAppsResponse = ListApplicationsResponse.builder() - .resource(ApplicationResource.builder() - .name(OLD_APPLICATION_NAME) - .id(APPLICATION_GUID.toString()) - .createdAt(APPLICATION_CREATED_AT) - .state(ApplicationState.STARTED) - .lifecycle(getApplicationLifecycle()) - .build()) - .pagination(Pagination.builder() - .totalResults(1) - .totalPages(1) - .build()) - .build(); - Mockito.when(applicationsV3.list(listAppsRequest)) - .thenReturn(Mono.just(listAppsResponse)); - Mockito.when(applicationsV3.update(updateAppRequest)) - .thenReturn(updateApplicationResponse); - var getAppRequest = GetApplicationRequest.builder() - .applicationId(APPLICATION_GUID.toString()) - .build(); - - Mockito.when(applicationsV3.get(getAppRequest)) - .thenReturn(getApplicationResponse); - } - - private void initControllerClientWithTargetSpace(UUID spaceGuid) { - CloudSpace target = ImmutableCloudSpace.builder() - .metadata(ImmutableCloudMetadata.builder() - .guid(spaceGuid) - .build()) - .build(); - this.controllerClient = new CloudControllerRestClientImpl(delegate, target); - } - - private ClientV3Exception clientV3Exception(int statusCode) { - return new ClientV3Exception(statusCode, Collections.emptyList()); - } -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java new file mode 100644 index 0000000000..3273433876 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java @@ -0,0 +1,1471 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudOrganization; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudOrganization; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ImmutableApplicationToCreateDto; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; +import org.springframework.web.client.RestClient; + +import static org.mockito.Mockito.when; + +class CloudControllerRestClientV3ImplTest { + + private static final String SPACE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String TASK_GUID = "99999999-8888-7777-6666-555555555555"; + private static final String BUILD_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String PACKAGE_GUID = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"; + private static final String SERVICE_INSTANCE_GUID = "cccccccc-dddd-eeee-ffff-000000000000"; + private static final String BINDING_GUID = "dddddddd-eeee-ffff-0000-111111111111"; + private static final String BROKER_GUID = "eeeeeeee-ffff-0000-1111-222222222222"; + private static final String SERVICE_KEY_GUID = "ffffffff-0000-1111-2222-333333333333"; + private static final String OFFERING_GUID = "a1a1a1a1-b2b2-c3c3-d4d4-e5e5e5e5e5e5"; + private static final String PLAN_GUID = "f6f6f6f6-a7a7-b8b8-c9c9-d0d0d0d0d0d0"; + private static final String DOMAIN_NAME = "example.com"; + private static final String APPS_BASE = MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50"; + private static final String SI_BASE = MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50"; + private static final String BINDINGS = MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings"; + + @Mock + private CloudSpace target; + @Mock + private CloudMetadata targetMetadata; + + private MockControllerClientFactory factory; + private CloudControllerRestClientV3Impl client; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + Function uploadRestClientFactory = duration -> factory.client() + .getRestClient(); + client = new CloudControllerRestClientV3Impl(target, factory.client(), uploadRestClientFactory); + } + + @Test + void testGetTargetReturnsInjectedSpace() { + Assertions.assertSame(target, client.getTarget()); + } + + @Test + void testGetStackReturnsMappedStack() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/stacks?names=cflinuxfs4&per_page=50", getStackListJson()); + + CloudStack result = client.getStack("cflinuxfs4"); + + Assertions.assertEquals("cflinuxfs4", result.getName()); + factory.verify(); + } + + @Test + void testGetStacksReturnsMappedList() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/stacks?per_page=50", getStackListJson()); + + List result = client.getStacks(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetDomainsReturnsMappedList() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=50", getDomainListJson()); + + List result = client.getDomains(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(DOMAIN_NAME, result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetSharedDomainsReturnsMappedList() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=50", getDomainListJson()); + + List result = client.getSharedDomains(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetApplicationReturnsMappedApplication() { + stubApplicationLookup(); + + CloudApplication result = client.getApplication("my-app"); + + Assertions.assertEquals("my-app", result.getName()); + Assertions.assertEquals(UUID.fromString(APP_GUID), result.getGuid()); + factory.verify(); + } + + @Test + void testGetApplicationsReturnsMappedList() { + when(target.getGuid()) + .thenReturn(null); + + stubGet(APPS_BASE, getAppListJson()); + + List result = client.getApplications(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetApplicationGuidReturnsGuid() { + stubApplicationLookup(); + + UUID result = client.getApplicationGuid("my-app"); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result); + factory.verify(); + } + + @Test + void testGetApplicationNameReturnsName() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, getAppJson()); + + String result = client.getApplicationName(UUID.fromString(APP_GUID)); + + Assertions.assertEquals("my-app", result); + factory.verify(); + } + + @Test + void testGetApplicationEnvironmentReturnsVariables() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", "{\"var\":{\"FOO\":\"bar\"}}"); + + Map result = client.getApplicationEnvironment(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(Map.of("FOO", "bar"), result); + factory.verify(); + } + + @Test + void testCreateApplicationWithoutStagingOrRoutesOnlyPosts() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getAppJson(), MediaType.APPLICATION_JSON)); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .build(); + + client.createApplication(dto); + + factory.verify(); + } + + @Test + void testCreateApplicationWithStagingUpdatesStagingThroughProcess() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getAppJson(), MediaType.APPLICATION_JSON)); + + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", getAppListJson()); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web", getWebProcessJson()); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .staging( + org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging.builder() + .command( + "run.sh") + .build()) + .build(); + + client.createApplication(dto); + + factory.verify(); + } + + @Test + void testGetEventsReturnsMappedList() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=50", getAuditEventListJson()); + + List result = client.getEvents(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("audit.app.update", result.getFirst() + .getType()); + factory.verify(); + } + + @Test + void testGetEventsByTargetReturnsMappedList() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=50" + + "&target_guids=" + APP_GUID, getAuditEventListJson()); + + List result = client.getEventsByTarget(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceInstanceReturnsMappedInstance() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + client.getServiceInstance("my-service", false); + + factory.verify(); + } + + @Test + void testGetServiceInstanceNameReturnsName() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, getServiceInstanceJson()); + + String result = client.getServiceInstanceName(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals("my-service", result); + factory.verify(); + } + + @Test + void testGetServiceInstanceWithoutAuxiliaryContentReturnsMappedInstance() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + CloudServiceInstance result = client.getServiceInstanceWithoutAuxiliaryContent("my-service"); + + Assertions.assertEquals("my-service", result.getName()); + factory.verify(); + } + + @Test + void testGetRequiredServiceInstanceGuidReturnsGuid() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + UUID result = client.getRequiredServiceInstanceGuid("my-service"); + + Assertions.assertEquals(UUID.fromString(SERVICE_INSTANCE_GUID), result); + factory.verify(); + } + + @Test + void testGetTaskReturnsMappedTask() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID, getTaskJson()); + + CloudTask result = client.getTask(UUID.fromString(TASK_GUID)); + + Assertions.assertEquals("migrate", result.getName()); + Assertions.assertEquals(CloudTask.State.SUCCEEDED, result.getState()); + factory.verify(); + } + + @Test + void testGetTasksReturnsMappedList() { + stubApplicationLookup(); + + stubGet(MockControllerClientFactory.BASE_URL + "/v3/tasks?per_page=50&app_guids=" + + APP_GUID, "{\"resources\":[" + getTaskJson() + "]}"); + + List result = client.getTasks("my-app"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetBuildReturnsMappedBuild() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID, getBuildJson()); + + CloudBuild result = client.getBuild(UUID.fromString(BUILD_GUID)); + + Assertions.assertEquals(CloudBuild.State.STAGED, result.getState()); + factory.verify(); + } + + @Test + void testGetBuildsForApplicationReturnsMappedList() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/builds?per_page=50", "{\"resources\":[" + getBuildJson() + "]}"); + + List result = client.getBuildsForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetBuildsForPackageReturnsMappedList() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + PACKAGE_GUID + + "&per_page=50", "{\"resources\":[" + getBuildJson() + "]}"); + + List result = client.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetApplicationRoutesReturnsMappedList() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/routes?per_page=50", "{\"resources\":[" + getRouteJson() + "]}"); + + List result = client.getApplicationRoutes(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetBuildPropagatesCloudOperationExceptionOnNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + UUID buildGuid = UUID.fromString(BUILD_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> client.getBuild(buildGuid)); + } + + @Test + void testGetApplicationWithRequiredFlag() { + stubApplicationLookup(); + + CloudApplication result = client.getApplication("my-app", false); + + Assertions.assertEquals("my-app", result.getName()); + factory.verify(); + } + + @Test + void testGetApplicationEnvironmentByName() { + stubApplicationLookup(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/environment_variables", + "{\"var\":{\"FOO\":\"bar\"}}"); + + Map result = client.getApplicationEnvironment("my-app"); + + Assertions.assertEquals(Map.of("FOO", "bar"), result); + factory.verify(); + } + + @Test + void testGetApplicationsByMetadataLabelSelector() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&label_selector=env%3Dprod", getAppListJson()); + + List result = client.getApplicationsByMetadataLabelSelector("env=prod"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testStartApplication() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/start", HttpMethod.POST); + + client.startApplication("my-app"); + + factory.verify(); + } + + @Test + void testStopApplication() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/stop", HttpMethod.POST); + + client.stopApplication("my-app"); + + factory.verify(); + } + + @Test + void testRestartApplication() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/stop", HttpMethod.POST); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/start", HttpMethod.POST); + + client.restartApplication("my-app"); + + factory.verify(); + } + + @Test + void testRenameApplication() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); + + client.rename("my-app", "new-name"); + + factory.verify(); + } + + @Test + void testUpdateApplicationInstances() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale", HttpMethod.POST); + + client.updateApplicationInstances("my-app", 3); + + factory.verify(); + } + + @Test + void testUpdateApplicationMemory() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale", HttpMethod.POST); + + client.updateApplicationMemory("my-app", 512); + + factory.verify(); + } + + @Test + void testUpdateApplicationDiskQuota() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale", HttpMethod.POST); + + client.updateApplicationDiskQuota("my-app", 2048); + + factory.verify(); + } + + @Test + void testUpdateApplicationEnv() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/environment_variables", HttpMethod.PATCH); + + client.updateApplicationEnv("my-app", Map.of("FOO", "bar")); + + factory.verify(); + } + + @Test + void testBindDropletToApp() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/relationships/current_droplet", HttpMethod.PATCH); + + client.bindDropletToApp(UUID.fromString(BUILD_GUID), UUID.fromString(APP_GUID)); + + factory.verify(); + } + + @Test + void testUpdateApplicationMetadata() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); + + client.updateApplicationMetadata(UUID.fromString(APP_GUID), ImmutableMetadata.builder() + .putLabel("key", "value") + .build()); + + factory.verify(); + } + + @Test + void testUpdateApplicationMetadataUnsetsLabelWhenValueIsNull() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andExpect(MockRestRequestMatchers.content() + .string(Matchers.containsString("\"obsolete\":null"))) + .andRespond(MockRestResponseCreators.withSuccess()); + + client.updateApplicationMetadata(UUID.fromString(APP_GUID), ImmutableMetadata.builder() + .putLabel("obsolete", null) + .build()); + + factory.verify(); + } + + @Test + void testDeleteApplication() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.DELETE); + + client.deleteApplication("my-app"); + + factory.verify(); + } + + @Test + void testGetApplicationProcess() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getWebProcessJson()); + + CloudProcess result = client.getApplicationProcess(UUID.fromString(APP_GUID)); + + Assertions.assertNotNull(result); + factory.verify(); + } + + @Test + void testGetApplicationInstancesByGuid() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/stats", + "{\"resources\":[{\"state\":\"RUNNING\",\"index\":0}]}"); + + InstancesInfo result = client.getApplicationInstances(UUID.fromString(APP_GUID)); + + Assertions.assertNotNull(result); + factory.verify(); + } + + @Test + void testGetApplicationFeatures() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=50", + "{\"resources\":[{\"name\":\"ssh\",\"enabled\":true}]}"); + + Map result = client.getApplicationFeatures(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(Boolean.TRUE, result.get("ssh")); + factory.verify(); + } + + @Test + void testGetCurrentDropletForApplication() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/droplets/current", + "{\"guid\":\"" + BUILD_GUID + "\",\"links\":{\"package\":{\"href\":\"https://api.example.com/v3/packages/" + PACKAGE_GUID + + "\"}}}"); + + DropletInfo result = client.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getPackageGuid()); + factory.verify(); + } + + @Test + void testUpdateApplicationStaging() { + stubApplicationLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getWebProcessJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/processes/" + APP_GUID, HttpMethod.PATCH); + + client.updateApplicationStaging("my-app", ImmutableStaging.builder() + .command("run.sh") + .build()); + + factory.verify(); + } + + @Test + void testGetApplicationEvents() { + stubApplicationLookup(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=50&target_guids=" + APP_GUID, + getAuditEventListJson()); + + List result = client.getApplicationEvents("my-app"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetDefaultDomain() { + stubTargetOrganization(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + SPACE_GUID + "/domains/default", + "{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}"); + + CloudDomain result = client.getDefaultDomain(); + + Assertions.assertEquals(DOMAIN_NAME, result.getName()); + factory.verify(); + } + + @Test + void testGetPrivateDomains() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=50", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + + "\",\"relationships\":{\"organization\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}}}]}"); + + List result = client.getPrivateDomains(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetDomainsForOrganization() { + stubTargetOrganization(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + SPACE_GUID + "/domains?per_page=50", + getDomainListJson()); + + List result = client.getDomainsForOrganization(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testAddDomainCreatesWhenMissing() { + stubTargetOrganization(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", + "{\"resources\":[]}"); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/domains", HttpMethod.POST); + + client.addDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testDeleteDomain() { + stubTargetOrganization(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", getDomainListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/domains/" + APP_GUID, HttpMethod.DELETE); + + client.deleteDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testAddRoute() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", getDomainListJson()); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getRouteJson(), MediaType.APPLICATION_JSON)); + + client.addRoute("myhost", DOMAIN_NAME, "/mypath"); + + factory.verify(); + } + + @Test + void testGetRoutes() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID, + "{\"resources\":[" + getRouteJson() + "]}"); + + List result = client.getRoutes(DOMAIN_NAME); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testDeleteOrphanedRoutes() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID + "/routes?unmapped=true", HttpMethod.DELETE); + + client.deleteOrphanedRoutes(); + + factory.verify(); + } + + @Test + void testDeleteRoute() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID + + "&hosts=myhost&paths=/mypath", "{\"resources\":[" + getRouteJson() + "]}"); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + APP_GUID, HttpMethod.DELETE); + + client.deleteRoute("myhost", DOMAIN_NAME, "/mypath"); + + factory.verify(); + } + + @Test + void testUpdateApplicationRoutesAddsRoute() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", "{\"resources\":[]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID + + "&hosts=myhost&paths=/mypath", "{\"resources\":[]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getRouteJson(), MediaType.APPLICATION_JSON)); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + APP_GUID + "/destinations", HttpMethod.POST); + + CloudRoute route = ImmutableCloudRoute.builder() + .domain(ImmutableCloudDomain.builder() + .name(DOMAIN_NAME) + .build()) + .host("myhost") + .path("/mypath") + .url("myhost.example.com/mypath") + .build(); + + client.updateApplicationRoutes("my-app", Set.of(route)); + + factory.verify(); + } + + @Test + void testGetServiceInstanceSingleArgument() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookupWithPlan(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + + "\",\"name\":\"my-plan\"}"); + + CloudServiceInstance result = client.getServiceInstance("my-service", false); + + Assertions.assertEquals("my-service", result.getName()); + factory.verify(); + } + + @Test + void testGetServiceInstanceWithoutAuxiliaryContentRequiredFlag() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + CloudServiceInstance result = client.getServiceInstanceWithoutAuxiliaryContent("my-service", false); + + Assertions.assertEquals("my-service", result.getName()); + factory.verify(); + } + + @Test + void testCreateServiceInstance() { + stubTargetSpaceMetadata(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=50&space_guids=" + SPACE_GUID + + "&names=my-offering", "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=50&service_offering_guids=" + OFFERING_GUID + + "&names=my-plan", "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\"}]}"); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances", HttpMethod.POST); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .label("my-offering") + .plan("my-plan") + .build(); + + client.createServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testCreateUserProvidedServiceInstance() { + stubTargetSpaceMetadata(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances", HttpMethod.POST); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-ups") + .build(); + + client.createUserProvidedServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testDeleteServiceInstanceByName() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.DELETE); + + client.deleteServiceInstance("my-service"); + + factory.verify(); + } + + @Test + void testDeleteServiceInstanceByInstance() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.DELETE); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .metadata(ImmutableCloudMetadata.builder() + .guid( + UUID.fromString( + SERVICE_INSTANCE_GUID)) + .build()) + .build(); + + client.deleteServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testGetServiceInstanceParameters() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID + "/parameters", + "{\"foo\":\"bar\"}"); + + Map result = client.getServiceInstanceParameters(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals("bar", result.get("foo")); + factory.verify(); + } + + @Test + void testGetUserProvidedServiceInstanceParameters() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID + "/credentials", + "{\"user\":\"admin\"}"); + + Map result = client.getUserProvidedServiceInstanceParameters(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals("admin", result.get("user")); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByNames() { + stubTargetSpaceMetadata(); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + + List result = client.getServiceInstancesWithoutAuxiliaryContentByNames(List.of("my-service")); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceInstancesByMetadataLabelSelector() { + stubTargetSpaceMetadata(); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&label_selector=env", + "{\"resources\":[{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\",\"name\":\"my-service\",\"type\":\"user-provided\"}]}"); + + List result = client.getServiceInstancesByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector() { + stubTargetSpaceMetadata(); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&label_selector=env", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + + List result = client.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testUpdateServiceParameters() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.PATCH); + + client.updateServiceParameters("my-service", Map.of("foo", "bar")); + + factory.verify(); + } + + @Test + void testUpdateServiceTags() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.PATCH); + + client.updateServiceTags("my-service", List.of("t1", "t2")); + + factory.verify(); + } + + @Test + void testUpdateServiceSyslogDrainUrlSkipsManaged() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + client.updateServiceSyslogDrainUrl("my-service", "syslog://x"); + + factory.verify(); + } + + @Test + void testUpdateServiceInstanceMetadata() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.PATCH); + + client.updateServiceInstanceMetadata(UUID.fromString(SERVICE_INSTANCE_GUID), ImmutableMetadata.builder() + .putLabel("k", "v") + .build()); + + factory.verify(); + } + + @Test + void testUpdateServiceInstanceMetadataUnsetsAnnotationWhenValueIsNull() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + + SERVICE_INSTANCE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andExpect(MockRestRequestMatchers.content() + .string(Matchers.containsString("\"obsolete\":null"))) + .andRespond(MockRestResponseCreators.withSuccess()); + + client.updateServiceInstanceMetadata(UUID.fromString(SERVICE_INSTANCE_GUID), ImmutableMetadata.builder() + .putAnnotation("obsolete", null) + .build()); + + factory.verify(); + } + + @Test + void testBindServiceInstance() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", getAppListJson()); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + stubMethod(BINDINGS, HttpMethod.POST); + + Optional result = client.bindServiceInstance("my-binding", "my-app", "my-service"); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testBindServiceInstanceWithParameters() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", getAppListJson()); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + stubMethod(BINDINGS, HttpMethod.POST); + + client.bindServiceInstance("my-binding", "my-app", "my-service", Map.of("p", "v")); + + factory.verify(); + } + + @Test + void testGetServiceBinding() { + stubGet(BINDINGS + "?guids=" + BINDING_GUID + "&per_page=50", + "{\"resources\":[" + getBindingJson() + "]}"); + + CloudServiceBinding result = client.getServiceBinding(UUID.fromString(BINDING_GUID)); + + Assertions.assertNotNull(result); + factory.verify(); + } + + @Test + void testGetServiceAppBindings() { + stubGet(BINDINGS + "?service_instance_guids=" + SERVICE_INSTANCE_GUID + "&type=app&per_page=50", + "{\"resources\":[" + getBindingJson() + "]}"); + + List result = client.getServiceAppBindings(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetAppBindings() { + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&per_page=50", "{\"resources\":[" + getBindingJson() + "]}"); + + List result = client.getAppBindings(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceBindingsForApplication() { + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=50", + "{\"resources\":[" + getBindingJson() + "]}"); + + List result = client.getServiceBindingsForApplication(UUID.fromString(APP_GUID), + UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceBindingParameters() { + stubGet(BINDINGS + "/" + BINDING_GUID + "/parameters", "{\"foo\":\"bar\"}"); + + Map result = client.getServiceBindingParameters(UUID.fromString(BINDING_GUID)); + + Assertions.assertEquals("bar", result.get("foo")); + factory.verify(); + } + + @Test + void testUpdateServiceBindingMetadata() { + stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.PATCH); + + client.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), ImmutableMetadata.builder() + .putLabel("k", "v") + .build()); + + factory.verify(); + } + + @Test + void testUpdateServiceBindingMetadataUnsetsLabelWhenValueIsNull() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(BINDINGS + "/" + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andExpect(MockRestRequestMatchers.content() + .string(Matchers.containsString("\"obsolete\":null"))) + .andRespond(MockRestResponseCreators.withSuccess()); + + client.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), ImmutableMetadata.builder() + .putLabel("obsolete", null) + .build()); + + factory.verify(); + } + + @Test + void testDeleteServiceBindingByGuid() { + stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.DELETE); + + Optional result = client.deleteServiceBinding(UUID.fromString(BINDING_GUID)); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testUnbindServiceInstanceByName() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", getAppListJson()); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=50", + "{\"resources\":[" + getBindingJson() + "]}"); + stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.DELETE); + + List result = client.unbindServiceInstance("my-app", "my-service"); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testUnbindServiceInstanceByGuid() { + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=50", + "{\"resources\":[" + getBindingJson() + "]}"); + stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.DELETE); + + client.unbindServiceInstance(UUID.fromString(APP_GUID), UUID.fromString(SERVICE_INSTANCE_GUID)); + + factory.verify(); + } + + @Test + void testGetServiceKey() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookupWithPlan(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + + "\",\"name\":\"my-plan\"}"); + stubGet(BINDINGS + "?per_page=50&type=key&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&names=my-key", + "{\"resources\":[" + getServiceKeyJson() + "]}"); + stubGet(BINDINGS + "/" + SERVICE_KEY_GUID + "/details", "{\"credentials\":{\"user\":\"admin\"}}"); + + CloudServiceKey result = client.getServiceKey("my-service", "my-key"); + + Assertions.assertNotNull(result); + factory.verify(); + } + + @Test + void testGetServiceKeysByName() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookupWithPlan(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + + "\",\"name\":\"my-plan\"}"); + stubGet(BINDINGS + "?per_page=50&type=key&service_instance_guids=" + SERVICE_INSTANCE_GUID, + "{\"resources\":[" + getServiceKeyJson() + "]}"); + + List result = client.getServiceKeys("my-service"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testCreateServiceKeyWithParameters() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookupWithPlan(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + + "\",\"name\":\"my-plan\"}"); + stubMethod(BINDINGS, HttpMethod.POST); + + Optional result = client.createServiceKey("my-service", "my-key", Map.of("p", "v")); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testCreateServiceBroker() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_brokers", HttpMethod.POST); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("my-broker") + .url("https://broker.example.com") + .username("user") + .password("pass") + .build(); + + Assertions.assertNull(client.createServiceBroker(broker)); + factory.verify(); + } + + @Test + void testGetServiceBroker() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); + + CloudServiceBroker result = client.getServiceBroker("my-broker"); + + Assertions.assertEquals("my-broker", result.getName()); + factory.verify(); + } + + @Test + void testGetServiceBrokerWithRequiredFlag() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); + + Assertions.assertNotNull(client.getServiceBroker("my-broker", false)); + factory.verify(); + } + + @Test + void testGetServiceBrokers() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?per_page=50", getBrokerListJson()); + + List result = client.getServiceBrokers(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testUpdateServiceBroker() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_brokers/" + BROKER_GUID, HttpMethod.PATCH); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("my-broker") + .url("https://broker.example.com") + .build(); + + client.updateServiceBroker(broker); + + factory.verify(); + } + + @Test + void testDeleteServiceBroker() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_brokers/" + BROKER_GUID, HttpMethod.DELETE); + + Assertions.assertNull(client.deleteServiceBroker("my-broker")); + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBroker() { + stubTargetSpaceMetadata(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + BROKER_GUID + "&space_guids=" + + SPACE_GUID + "&per_page=50", "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + "&per_page=50", + "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID + "/visibility", HttpMethod.PATCH); + + client.updateServicePlanVisibilityForBroker("my-broker", ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + @Test + void testGetServiceOfferings() { + when(target.getMetadata()).thenReturn(ImmutableCloudMetadata.of(UUID.fromString(SPACE_GUID))); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=50&space_guids=" + SPACE_GUID, + "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + "&per_page=50", + "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\"}]}"); + + List result = client.getServiceOfferings(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testRunTask() { + stubApplicationLookup(); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/tasks")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson(), MediaType.APPLICATION_JSON)); + + CloudTask task = ImmutableCloudTask.builder() + .name("migrate") + .command("rails db:migrate") + .build(); + + CloudTask result = client.runTask("my-app", task); + + Assertions.assertEquals("migrate", result.getName()); + factory.verify(); + } + + @Test + void testCancelTask() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID + "/actions/cancel")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson(), MediaType.APPLICATION_JSON)); + + CloudTask result = client.cancelTask(UUID.fromString(TASK_GUID)); + + Assertions.assertEquals("migrate", result.getName()); + factory.verify(); + } + + @Test + void testCreateBuild() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getBuildJson(), MediaType.APPLICATION_JSON)); + + CloudBuild result = client.createBuild(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(CloudBuild.State.STAGED, result.getState()); + factory.verify(); + } + + @Test + void testGetPackage() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, getPackageJson()); + + CloudPackage result = client.getPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getGuid()); + factory.verify(); + } + + @Test + void testGetPackagesForApplication() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=50", + "{\"resources\":[" + getPackageJson() + "]}"); + + List result = client.getPackagesForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetUserRolesBySpaceAndUser() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/roles?per_page=50&space_guids=" + SPACE_GUID + "&user_guids=" + APP_GUID, + "{\"resources\":[{\"guid\":\"" + BINDING_GUID + "\",\"type\":\"space_developer\"}]}"); + + Set result = client.getUserRolesBySpaceAndUser(UUID.fromString(SPACE_GUID), UUID.fromString(APP_GUID)); + + Assertions.assertFalse(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetAsyncJob() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/job-123", + "{\"guid\":\"job-123\",\"operation\":\"app.delete\",\"state\":\"COMPLETE\"}"); + + Assertions.assertNotNull(client.getAsyncJob("job-123")); + factory.verify(); + } + + private void stubTargetSpaceMetadata() { + when(target.getMetadata()) + .thenReturn(targetMetadata); + when(targetMetadata.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + } + + private void stubServiceInstanceLookup() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50&space_guids=" + SPACE_GUID + + "&names=my-service")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getServiceInstanceJson() + "]}", + MediaType.APPLICATION_JSON)); + } + + private void stubServiceInstanceLookupWithPlan() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50&space_guids=" + SPACE_GUID + + "&names=my-service")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getServiceInstanceWithPlanJson() + "]}", + MediaType.APPLICATION_JSON)); + } + + private void stubTargetOrganization() { + CloudOrganization organization = ImmutableCloudOrganization.builder() + .name("my-org") + .metadata( + ImmutableCloudMetadata.builder() + .guid( + UUID.fromString( + SPACE_GUID)) + .build()) + .build(); + when(target.getOrganization()).thenReturn(organization); + } + + private void stubGet(String uri, String responseJson) { + factory.stubGet(uri, responseJson); + } + + private void stubApplicationLookup() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + } + + private void stubMethod(String uri, HttpMethod method) { + factory.stubMethod(uri, method); + } + + private static String getStackListJson() { + return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"cflinuxfs4\",\"description\":\"Ubuntu\"}]}"; + } + + private static String getDomainListJson() { + return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"; + } + + private static String getAppListJson() { + return "{\"resources\":[" + getAppJson() + "]}"; + } + + private static String getAppJson() { + return "{\"guid\":\"" + APP_GUID + "\",\"name\":\"my-app\",\"state\":\"STARTED\"," + + "\"lifecycle\":{\"type\":\"buildpack\",\"data\":{\"buildpacks\":[\"java_buildpack\"],\"stack\":\"cflinuxfs4\"}}}"; + } + + private static String getWebProcessJson() { + return "{\"guid\":\"" + APP_GUID + "\",\"type\":\"web\",\"command\":\"old.sh\",\"instances\":1,\"memory_in_mb\":256," + + "\"disk_in_mb\":1024}"; + } + + private static String getAuditEventListJson() { + return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"type\":\"audit.app.update\"," + + "\"actor\":{\"guid\":\"" + APP_GUID + "\",\"type\":\"user\",\"name\":\"admin\"}," + + "\"target\":{\"guid\":\"" + APP_GUID + "\",\"type\":\"app\",\"name\":\"my-app\"}}]}"; + } + + private static String getServiceInstanceJson() { + return "{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\",\"name\":\"my-service\",\"type\":\"managed\"}"; + } + + private static String getServiceInstanceWithPlanJson() { + return "{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\",\"name\":\"my-service\",\"type\":\"managed\",\"relationships\":" + + "{\"service_plan\":{\"data\":{\"guid\":\"" + PLAN_GUID + "\"}}}}"; + } + + private static String getBindingJson() { + return "{\"guid\":\"" + BINDING_GUID + "\",\"name\":\"my-binding\",\"type\":\"app\",\"relationships\":" + + "{\"app\":{\"data\":{\"guid\":\"" + APP_GUID + "\"}},\"service_instance\":{\"data\":{\"guid\":\"" + + SERVICE_INSTANCE_GUID + "\"}}}}"; + } + + private static String getServiceKeyJson() { + return "{\"guid\":\"" + SERVICE_KEY_GUID + "\",\"name\":\"my-key\",\"type\":\"key\",\"relationships\":" + + "{\"service_instance\":{\"data\":{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\"}}}}"; + } + + private static String getBrokerListJson() { + return "{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\",\"name\":\"my-broker\",\"url\":\"https://broker.example.com\"}]}"; + } + + private static String getPackageJson() { + return "{\"guid\":\"" + PACKAGE_GUID + "\",\"type\":\"bits\",\"state\":\"READY\"}"; + } + + private static String getTaskJson() { + return "{\"guid\":\"" + TASK_GUID + "\",\"name\":\"migrate\",\"command\":\"rails db:migrate\",\"state\":\"SUCCEEDED\"," + + "\"memory_in_mb\":256,\"disk_in_mb\":1024}"; + } + + private static String getBuildJson() { + return "{\"guid\":\"" + BUILD_GUID + "\",\"state\":\"STAGED\",\"package\":{\"guid\":\"" + PACKAGE_GUID + "\"}," + + "\"droplet\":{\"guid\":\"" + APP_GUID + "\"}}"; + } + + private static String getRouteJson() { + return "{\"guid\":\"" + APP_GUID + "\",\"host\":\"myhost\",\"path\":\"/mypath\",\"url\":\"myhost.example.com/mypath\"," + + "\"destinations\":[],\"relationships\":{\"space\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}," + + "\"domain\":{\"data\":{\"guid\":\"" + APP_GUID + "\"}}}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java new file mode 100644 index 0000000000..a9a385f183 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java @@ -0,0 +1,39 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class CloudControllerV3ClientPageUriTest { + + @Test + void testPageUriAddsPageWhenAbsentAndKeepsExistingQuery() { + String uri = CloudControllerV3Client.pageUri("/v3/apps?per_page=5000&space_guids=abc", 3); + + Assertions.assertTrue(uri.contains("page=3"), uri); + Assertions.assertTrue(uri.contains("per_page=5000"), uri); + Assertions.assertTrue(uri.contains("space_guids=abc"), uri); + } + + @Test + void testPageUriReplacesExistingPageParam() { + String uri = CloudControllerV3Client.pageUri("/v3/apps?per_page=5000&page=1", 4); + + Assertions.assertTrue(uri.contains("page=4"), uri); + Assertions.assertFalse(uri.contains("page=1"), uri); + Assertions.assertTrue(uri.contains("per_page=5000"), uri); + } + + @Test + void testPageUriPreservesMultipleFilters() { + String first = "/v3/service_credential_bindings?per_page=5000&type=key&service_instance_guids=g1&names=n1"; + + String uri = CloudControllerV3Client.pageUri(first, 2); + + Assertions.assertTrue(uri.contains("page=2"), uri); + Assertions.assertTrue(uri.contains("type=key"), uri); + Assertions.assertTrue(uri.contains("service_instance_guids=g1"), uri); + Assertions.assertTrue(uri.contains("names=n1"), uri); + Assertions.assertTrue(uri.contains("per_page=5000"), uri); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java new file mode 100644 index 0000000000..18402e7021 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java @@ -0,0 +1,144 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.time.Duration; +import java.util.Optional; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class CloudControllerV3ClientTest { + + private static final String JOB_GUID = "66666666-6666-6666-6666-666666666666"; + private static final Duration TIMEOUT = Duration.ofMillis(10); + + private MockControllerClientFactory factory; + private CloudControllerV3Client client; + + @BeforeEach + void setUp() { + factory = MockControllerClientFactory.create(); + client = factory.client(); + } + + @Test + void testGetReturnsMappedBody() { + stubJobGet(getJobJson("COMPLETE")); + + V3Job job = client.get("/v3/jobs/" + JOB_GUID, V3Job.class); + + Assertions.assertEquals(JOB_GUID, job.guid()); + Assertions.assertEquals("COMPLETE", job.state()); + factory.verify(); + } + + @Test + void testGetThrowsOnNotFound() { + stubJobGetStatus(HttpStatus.NOT_FOUND); + + Assertions.assertThrows(CloudOperationException.class, () -> client.get("/v3/jobs/" + JOB_GUID, V3Job.class)); + } + + @Test + void testGetOptionalReturnsPresentOn200() { + stubJobGet(getJobJson("COMPLETE")); + + Optional job = client.getOptional("/v3/jobs/" + JOB_GUID, V3Job.class); + + Assertions.assertTrue(job.isPresent()); + Assertions.assertEquals(JOB_GUID, job.get() + .guid()); + factory.verify(); + } + + @Test + void testGetOptionalReturnsEmptyOn404() { + stubJobGetStatus(HttpStatus.NOT_FOUND); + + Optional job = client.getOptional("/v3/jobs/" + JOB_GUID, V3Job.class); + + Assertions.assertTrue(job.isEmpty()); + factory.verify(); + } + + @Test + void testGetOptionalRethrowsNonNotFound() { + stubJobGetStatus(HttpStatus.INTERNAL_SERVER_ERROR); + + Assertions.assertThrows(CloudOperationException.class, () -> client.getOptional("/v3/jobs/" + JOB_GUID, V3Job.class)); + } + + @Test + void testWaitForAsyncJobReturnsWhenComplete() { + stubJobGet(getJobJson("COMPLETE")); + + V3Job job = client.waitForAsyncJob("/" + JOB_GUID, TIMEOUT); + + Assertions.assertEquals(JOB_GUID, job.guid()); + Assertions.assertTrue(job.isComplete()); + factory.verify(); + } + + @Test + void testWaitForAsyncJobThrowsWhenFailed() { + stubJobGet(getFailedJobJson()); + + CloudOperationException exception = Assertions.assertThrows(CloudOperationException.class, + () -> client.waitForAsyncJob("/" + JOB_GUID, TIMEOUT)); + + Assertions.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, exception.getStatusCode()); + factory.verify(); + } + + @Test + void testFollowAsyncJobFollowsLocation() { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)); + ResponseEntity accepted = new ResponseEntity<>(headers, HttpStatus.ACCEPTED); + + stubJobGet(getJobJson("COMPLETE")); + + client.followAsyncJob(accepted, TIMEOUT); + + factory.verify(); + } + + @Test + void testFollowAsyncJobReturnsImmediatelyWhenNoLocation() { + ResponseEntity created = new ResponseEntity<>(new HttpHeaders(), HttpStatus.CREATED); + + client.followAsyncJob(created, TIMEOUT); + + factory.verify(); + } + + private void stubJobGet(String jobJson) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID, jobJson); + } + + private void stubJobGetStatus(HttpStatus status) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(status)); + } + + private static String getJobJson(String state) { + return "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"" + state + "\"}"; + } + + private static String getFailedJobJson() { + return "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"FAILED\",\"errors\":[{\"detail\":\"boom\",\"title\":\"CF-JobFailed\"," + + "\"code\":10008}]}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java new file mode 100644 index 0000000000..d7c5145396 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java @@ -0,0 +1,117 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class CloudSpaceClientTest { + + private static final String SPACE_GUID = "11111111-1111-1111-1111-111111111111"; + private static final String ORG_GUID = "22222222-2222-2222-2222-222222222222"; + private static final String SPACE_NAME = "my-space"; + private static final String ORG_NAME = "my-org"; + + private MockControllerClientFactory factory; + private CloudSpaceClient client; + + @BeforeEach + void setUp() { + factory = MockControllerClientFactory.create(); + client = new CloudSpaceClient(factory.client()); + } + + @Test + void testGetSpaceByGuidReturnsMappedSpace() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID, getSpaceJson()); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID, getOrgJson()); + + CloudSpace result = client.getSpace(UUID.fromString(SPACE_GUID)); + + Assertions.assertEquals(SPACE_NAME, result.getName()); + Assertions.assertEquals(UUID.fromString(SPACE_GUID), result.getGuid()); + Assertions.assertEquals(ORG_NAME, result.getOrganization() + .getName()); + Assertions.assertEquals(UUID.fromString(ORG_GUID), result.getOrganization() + .getGuid()); + factory.verify(); + } + + @Test + void testGetSpaceByGuidThrowsWhenSpaceNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + UUID spaceGuid = UUID.fromString(SPACE_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(spaceGuid)); + } + + @Test + void testGetSpaceByGuidThrowsWhenOrganizationNotFound() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID, getSpaceJson()); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + UUID spaceGuid = UUID.fromString(SPACE_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(spaceGuid)); + } + + @Test + void testGetSpaceByNameReturnsMappedSpace() { + stubOrgLookupWithResult(); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/spaces?organization_guids=" + ORG_GUID + + "&names=" + SPACE_NAME, "{\"resources\":[" + getSpaceJson() + "]}"); + + CloudSpace result = client.getSpace(ORG_NAME, SPACE_NAME); + + Assertions.assertEquals(SPACE_NAME, result.getName()); + Assertions.assertEquals(ORG_NAME, result.getOrganization() + .getName()); + factory.verify(); + } + + @Test + void testGetSpaceByNameThrowsWhenOrganizationNotFound() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations?names=" + ORG_NAME, "{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(ORG_NAME, SPACE_NAME)); + } + + @Test + void testGetSpaceByNameThrowsWhenSpaceNotFound() { + stubOrgLookupWithResult(); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/spaces?organization_guids=" + ORG_GUID + + "&names=" + SPACE_NAME, "{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(ORG_NAME, SPACE_NAME)); + } + + private void stubOrgLookupWithResult() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations?names=" + ORG_NAME, + "{\"resources\":[" + getOrgJson() + "]}"); + } + + private static String getSpaceJson() { + return "{\"guid\":\"" + SPACE_GUID + "\",\"name\":\"" + SPACE_NAME + "\"," + + "\"relationships\":{\"organization\":{\"data\":{\"guid\":\"" + ORG_GUID + "\"}}}}"; + } + + private static String getOrgJson() { + return "{\"guid\":\"" + ORG_GUID + "\",\"name\":\"" + ORG_NAME + "\"}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java new file mode 100644 index 0000000000..9e7fb1d13d --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java @@ -0,0 +1,258 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudOrganization; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudOrganization; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3DomainRelationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3ToOneRelationship; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import static org.mockito.Mockito.when; + +class DomainsV3OperationsTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String ORG_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String DOMAIN_GUID = "22222222-2222-2222-2222-222222222222"; + private static final String JOB_GUID = "66666666-6666-6666-6666-666666666666"; + private static final String DOMAIN_NAME = "example.com"; + + @Mock + private CloudControllerV3Client client; + @Mock + private CloudSpace targetSpace; + + private DomainsV3Operations operations; + + private MockControllerClientFactory factory; + private DomainsV3Operations serverOperations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + operations = new DomainsV3Operations(client, targetSpace); + factory = MockControllerClientFactory.create(); + serverOperations = new DomainsV3Operations(factory.client(), targetSpace); + } + + @Test + void testGetDomainsMapsAllResults() { + mockDomainList(getSharedDomain("shared.example.com"), getPrivateDomain("private.example.com")); + + List result = operations.getDomains(); + + Assertions.assertEquals(2, result.size()); + } + + @Test + void testGetSharedDomainsReturnsOnlyNonPrivate() { + mockDomainList(getSharedDomain("shared.example.com"), getPrivateDomain("private.example.com")); + + List result = operations.getSharedDomains(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("shared.example.com", result.getFirst() + .getName()); + } + + @Test + void testGetPrivateDomainsReturnsOnlyPrivate() { + mockDomainList(getSharedDomain("shared.example.com"), getPrivateDomain("private.example.com")); + + List result = operations.getPrivateDomains(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("private.example.com", result.getFirst() + .getName()); + } + + @Test + void testGetSharedDomainsReturnsEmptyWhenNoDomains() { + mockDomainList(); + + Assertions.assertTrue(operations.getSharedDomains() + .isEmpty()); + } + + @Test + void testAddDomainCreatesWhenAbsent() { + when(targetSpace.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); + + stubEmptyDomainLookup(DOMAIN_NAME); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/domains")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME), + MediaType.APPLICATION_JSON)); + + serverOperations.addDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testAddDomainSkipsCreationWhenDomainAlreadyExists() { + stubDomainLookupWithResult(DOMAIN_NAME, DOMAIN_GUID); + + serverOperations.addDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testAddDomainThrowsWhenOrganizationMissing() { + stubEmptyDomainLookup(DOMAIN_NAME); + + Assertions.assertThrows(CloudOperationException.class, () -> serverOperations.addDomain(DOMAIN_NAME)); + } + + @Test + void testAddDomainThrowsWhenSpaceNotProvided() { + DomainsV3Operations noSpacedDomainOperations = new DomainsV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpacedDomainOperations.addDomain(DOMAIN_NAME)); + } + + @Test + void testDeleteDomainDeletesFoundDomain() { + stubDomainLookupWithResult(DOMAIN_NAME, DOMAIN_GUID); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/domains/" + DOMAIN_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID, + "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}"); + + serverOperations.deleteDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testDeleteDomainThrowsWhenDomainNotFound() { + stubEmptyDomainLookup(DOMAIN_NAME); + + Assertions.assertThrows(CloudOperationException.class, () -> serverOperations.deleteDomain(DOMAIN_NAME)); + } + + @Test + void testDeleteDomainThrowsWhenSpaceNotProvided() { + DomainsV3Operations noSpaceOperations = new DomainsV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.deleteDomain(DOMAIN_NAME)); + } + + @Test + void testGetDefaultDomainReturnsMappedDomain() { + when(targetSpace.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains/default", + getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME)); + + CloudDomain result = serverOperations.getDefaultDomain(); + + Assertions.assertEquals(DOMAIN_NAME, result.getName()); + factory.verify(); + } + + @Test + void testGetDomainsForOrganizationReturnsMappedDomains() { + when(targetSpace.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=50", + "{\"resources\":[" + getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME) + "]}"); + + List result = serverOperations.getDomainsForOrganization(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(DOMAIN_NAME, result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetDomainsForOrganizationReturnsEmptyWhenNoDomains() { + when(targetSpace.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=50", + "{\"resources\":[]}"); + + Assertions.assertTrue(serverOperations.getDomainsForOrganization() + .isEmpty()); + factory.verify(); + } + + @Test + void testGetDomainsForOrganizationThrowsWhenSpaceNotProvided() { + DomainsV3Operations noSpaceOperations = new DomainsV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, noSpaceOperations::getDomainsForOrganization); + } + + private void stubEmptyDomainLookup(String name) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=50", + "{\"resources\":[]}"); + } + + private void stubDomainLookupWithResult(String name, String domainGuid) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=50", + "{\"resources\":[" + getDomainResourceJson(domainGuid, name) + "]}"); + } + + private static CloudOrganization getOrganizationWithGuid(String guid) { + return ImmutableCloudOrganization.builder() + .metadata(ImmutableCloudMetadata.of(UUID.fromString(guid))) + .name("my-org") + .build(); + } + + private static String getDomainResourceJson(String guid, String name) { + return "{\"guid\":\"" + guid + "\",\"name\":\"" + name + "\"}"; + } + + private void mockDomainList(V3Domain... domains) { + when(client.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())) + .thenReturn(List.of(domains)); + } + + private static V3Domain getSharedDomain(String name) { + return new V3Domain(GUID_STRING, name, null, null, null, null, null); + } + + private static V3Domain getPrivateDomain(String name) { + V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(new V3RelationshipData(ORG_GUID))); + + return new V3Domain(GUID_STRING, name, null, null, null, null, relationships); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java new file mode 100644 index 0000000000..1d8c98c075 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java @@ -0,0 +1,121 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEvent; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class EventsV3OperationsTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); + private static final UUID TARGET_GUID = UUID.fromString("99999999-8888-7777-6666-555555555555"); + + @Mock + private CloudControllerV3Client client; + @Mock + private CloudSpace targetSpace; + + private EventsV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + operations = new EventsV3Operations(client, targetSpace); + } + + @Test + void testGetEventsMapsResults() { + when(client.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())).thenReturn( + List.of(getAuditEvent("audit.app.update"), getAuditEvent("audit.app.create"))); + + List result = operations.getEvents(); + + Assertions.assertEquals(2, result.size()); + Assertions.assertEquals("audit.app.update", result.getFirst() + .getType()); + } + + @Test + void testGetEventsByTargetIncludesTargetGuidInQuery() { + when( + client.list(ArgumentMatchers.anyString(), ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + operations.getEventsByTarget(TARGET_GUID); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + verify(client) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("/v3/audit_events"), uriCaptor.getValue()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("target_guids=" + TARGET_GUID), uriCaptor.getValue()); + } + + @Test + void testGetApplicationEventsThrowsWhenApplicationNotFound() { + when(targetSpace.getGuid()) + .thenReturn(SPACE_GUID); + + when(client.list(ArgumentMatchers.contains("/v3/apps"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, + () -> operations.getApplicationEvents("missing-app")); + + Assertions.assertEquals(HttpStatus.NOT_FOUND, thrown.getStatusCode()); + } + + @Test + void testGetApplicationEventsQueryScopesAppsToSpaceWhenTargetPresent() { + when(targetSpace.getGuid()) + .thenReturn(SPACE_GUID); + when(client.list(ArgumentMatchers.contains("/v3/apps"), + ArgumentMatchers.>> any())) + .thenReturn(List.of(new V3GuidReference(GUID_STRING))); + when(client.list(ArgumentMatchers.contains("/v3/audit_events"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + operations.getApplicationEvents("my-app"); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + verify(client, times(2)) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + String appsQuery = uriCaptor.getAllValues() + .stream() + .filter(uri -> uri.contains("/v3/apps")) + .findFirst() + .orElseThrow(); + Assertions.assertTrue(appsQuery.contains("space_guids=" + SPACE_GUID), appsQuery); + Assertions.assertTrue(appsQuery.contains("names=my-app"), appsQuery); + } + + private static V3AuditEvent getAuditEvent(String type) { + return new V3AuditEvent(GUID_STRING, null, null, type, new V3AuditEvent.V3Participant(GUID_STRING, "user", "admin"), + new V3AuditEvent.V3Participant(GUID_STRING, "app", "my-app")); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java new file mode 100644 index 0000000000..8f0a71354f --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java @@ -0,0 +1,70 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; +import org.springframework.web.client.RestClient; + +final class MockControllerClientFactory { + + static final String BASE_URL = "https://api.cf.test.base.com"; + + private final CloudControllerV3Client client; + private final MockRestServiceServer server; + + private MockControllerClientFactory(CloudControllerV3Client client, MockRestServiceServer server) { + this.client = client; + this.server = server; + } + + static MockControllerClientFactory create() { + RestClient.Builder builder = RestClient.builder() + .baseUrl(BASE_URL) + .messageConverters(converters -> { + converters.clear(); + converters.add(new ByteArrayHttpMessageConverter()); + converters.add(new StringHttpMessageConverter()); + converters.add(new MappingJackson2HttpMessageConverter(new ObjectMapper())); + }) + .defaultStatusHandler(new CloudControllerResponseErrorHandler()); + MockRestServiceServer server = MockRestServiceServer.bindTo(builder) + .build(); + + CloudControllerV3Client client = new CloudControllerV3Client(builder.build()); + return new MockControllerClientFactory(client, server); + } + + CloudControllerV3Client client() { + return client; + } + + MockRestServiceServer server() { + return server; + } + + void stubGet(String uri, String responseJson) { + var response = responseJson == null ? MockRestResponseCreators.withSuccess() + : MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON); + + server.expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(response); + } + + void stubMethod(String uri, HttpMethod method) { + server.expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(method)) + .andRespond(MockRestResponseCreators.withSuccess()); + } + + void verify() { + server.verify(); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java new file mode 100644 index 0000000000..9fa0cf7e8c --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java @@ -0,0 +1,177 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerCredentials; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class PackagesV3OperationsTest { + + private static final String PACKAGE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String APP_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private PackagesV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new PackagesV3Operations(factory.client(), target); + } + + @Test + void testGetPackageReturnsMappedPackage() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, bitsPackageJson("READY", null)); + + CloudPackage result = operations.getPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(CloudPackage.Type.BITS, result.getType()); + Assertions.assertEquals(Status.READY, result.getStatus()); + Assertions.assertEquals(PACKAGE_GUID, result.getGuid() + .toString()); + factory.verify(); + } + + @Test + void testGetPackageThrowsWhenResponseIsNull() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, ""); + + UUID packageGuid = UUID.fromString(PACKAGE_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> operations.getPackage(packageGuid)); + } + + @Test + void testGetPackageThrowsOnNotFoundStatus() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + UUID packageGuid = UUID.fromString(PACKAGE_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> operations.getPackage(packageGuid)); + } + + @Test + void testGetPackagesForApplicationReturnsMappedList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=50", + "{\"resources\":[" + bitsPackageJson("READY", null) + "]}"); + + List result = operations.getPackagesForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(Status.READY, result.getFirst() + .getStatus()); + factory.verify(); + } + + @Test + void testGetPackagesForApplicationReturnsEmptyList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=50", + "{\"resources\":[]}"); + + List result = operations.getPackagesForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + @Test + void testCreateDockerPackagePostsAndReturnsCreatedPackage() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, dockerPackageJson()); + + DockerInfo dockerInfo = ImmutableDockerInfo.builder() + .image("my/image:latest") + .build(); + + CloudPackage result = operations.createDockerPackage(UUID.fromString(APP_GUID), dockerInfo); + + Assertions.assertEquals(CloudPackage.Type.DOCKER, result.getType()); + Assertions.assertEquals(PACKAGE_GUID, result.getGuid() + .toString()); + factory.verify(); + } + + @Test + void testCreateDockerPackageWithCredentials() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, dockerPackageJson()); + + DockerInfo dockerInfo = ImmutableDockerInfo.builder() + .image("my/image:latest") + .credentials(ImmutableDockerCredentials.builder() + .username("user") + .password("secret") + .build()) + .build(); + + CloudPackage result = operations.createDockerPackage(UUID.fromString(APP_GUID), dockerInfo); + + Assertions.assertEquals(CloudPackage.Type.DOCKER, result.getType()); + factory.verify(); + } + + @Test + void testGetUploadStatusForBitsPackageIncludesErrorDetails() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, bitsPackageJson("FAILED", "boom")); + + Upload result = operations.getUploadStatus(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(Status.FAILED, result.getStatus()); + Assertions.assertEquals("boom", result.getErrorDetails() + .getDescription()); + factory.verify(); + } + + @Test + void testGetUploadStatusForDockerPackageHasNoErrorDetails() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, dockerPackageJson()); + + Upload result = operations.getUploadStatus(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(Status.READY, result.getStatus()); + Assertions.assertNull(result.getErrorDetails()); + factory.verify(); + } + + private static String bitsPackageJson(String state, String error) { + String data = error == null ? "{}" : "{\"error\":\"" + error + "\"}"; + return "{\"guid\":\"" + PACKAGE_GUID + "\",\"type\":\"bits\",\"state\":\"" + state + "\",\"data\":" + data + "}"; + } + + private static String dockerPackageJson() { + return "{\"guid\":\"" + PACKAGE_GUID + + "\",\"type\":\"docker\",\"state\":\"READY\",\"data\":{\"image\":\"my/image:latest\"}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java new file mode 100644 index 0000000000..4f538824d3 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java @@ -0,0 +1,341 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import static org.mockito.Mockito.when; + +class ProcessesV3OperationsTest { + + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String PROCESS_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String PACKAGE_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String DROPLET_GUID = "99999999-8888-7777-6666-555555555555"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private ProcessesV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ProcessesV3Operations(factory.client(), target); + } + + @Test + void testGetApplicationProcessReturnsMappedProcess() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getProcessJson()); + + CloudProcess result = operations.getApplicationProcess(UUID.fromString(APP_GUID)); + + Assertions.assertEquals("start-cmd", result.getCommand()); + Assertions.assertEquals(2, result.getInstances()); + Assertions.assertEquals(256, result.getMemoryInMb()); + Assertions.assertEquals(1024, result.getDiskInMb()); + Assertions.assertEquals(HealthCheckType.HTTP, result.getHealthCheckType()); + Assertions.assertEquals("/health", result.getHealthCheckHttpEndpoint()); + factory.verify(); + } + + @Test + void testGetApplicationProcessReturnsNullWhenNoBody() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NO_CONTENT)); + + CloudProcess result = operations.getApplicationProcess(UUID.fromString(APP_GUID)); + + Assertions.assertNull(result); + factory.verify(); + } + + @Test + void testGetApplicationInstancesByGuidReturnsMappedInstances() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/stats", getStatsJson()); + + InstancesInfo result = operations.getApplicationInstances(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(2, result.getInstances() + .size()); + Assertions.assertEquals(InstanceState.RUNNING, result.getInstances() + .get(0) + .getState()); + Assertions.assertEquals(InstanceState.CRASHED, result.getInstances() + .get(1) + .getState()); + factory.verify(); + } + + @Test + void testGetApplicationInstancesForStartedApplicationQueriesStats() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/stats", getStatsJson()); + + CloudApplication application = ImmutableCloudApplication.builder() + .metadata(ImmutableCloudMetadata.builder() + .guid(UUID.fromString(APP_GUID)) + .build()) + .name("my-app") + .state(CloudApplication.State.STARTED) + .build(); + + InstancesInfo result = operations.getApplicationInstances(application); + + Assertions.assertEquals(2, result.getInstances() + .size()); + factory.verify(); + } + + @Test + void testGetApplicationInstancesForStoppedApplicationReturnsEmptyWithoutHttp() { + CloudApplication application = ImmutableCloudApplication.builder() + .metadata(ImmutableCloudMetadata.builder() + .guid(UUID.fromString(APP_GUID)) + .build()) + .name("my-app") + .state(CloudApplication.State.STOPPED) + .build(); + + InstancesInfo result = operations.getApplicationInstances(application); + + Assertions.assertTrue(result.getInstances() + .isEmpty()); + factory.verify(); + } + + @Test + void testGetApplicationFeaturesReturnsMap() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=50", + "{\"resources\":[{\"name\":\"ssh\",\"enabled\":true},{\"name\":\"revisions\",\"enabled\":false}]}"); + + Map result = operations.getApplicationFeatures(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(2, result.size()); + Assertions.assertTrue(result.get("ssh")); + Assertions.assertFalse(result.get("revisions")); + factory.verify(); + } + + @Test + void testGetApplicationFeaturesReturnsEmptyMap() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=50", "{\"resources\":[]}"); + + Map result = operations.getApplicationFeatures(UUID.fromString(APP_GUID)); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetCurrentDropletForApplicationReturnsDropletInfo() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/droplets/current", + getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)); + + DropletInfo result = operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(UUID.fromString(DROPLET_GUID), result.getGuid()); + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getPackageGuid()); + factory.verify(); + } + + @Test + void testGetCurrentDropletForApplicationHandlesTrailingSlashInPackageLink() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/droplets/current", + getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID + "/")); + + DropletInfo result = operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getPackageGuid()); + factory.verify(); + } + + @Test + void testGetCurrentDropletForApplicationThrowsWhenNoDroplet() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/droplets/current")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NO_CONTENT)); + + UUID appGuid = UUID.fromString(APP_GUID); + Assertions.assertThrows(CloudOperationException.class, + () -> operations.getCurrentDropletForApplication(appGuid)); + } + + @Test + void testUpdateApplicationStagingUpdatesAppAndProcess() { + when(target.getGuid()) + .thenReturn(null); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=my-app", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getProcessJson()); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + PROCESS_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + Staging staging = ImmutableStaging.builder() + .command("start-cmd") + .stackName("cflinuxfs4") + .addBuildpacks("java_buildpack") + .healthCheckType("http") + .healthCheckHttpEndpoint("/health") + .healthCheckTimeout(60) + .readinessHealthCheckType("http") + .readinessHealthCheckHttpEndpoint("/ready") + .build(); + + operations.updateApplicationStaging("my-app", staging); + + factory.verify(); + } + + @Test + void testUpdateApplicationStagingUpdatesAppFeatures() { + when(target.getGuid()) + .thenReturn(null); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=my-app", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/features/ssh")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getProcessJson()); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + PROCESS_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + Staging staging = ImmutableStaging.builder() + .command("start-cmd") + .appFeatures(Map.of("ssh", true)) + .build(); + + operations.updateApplicationStaging("my-app", staging); + + factory.verify(); + } + + @Test + void testUpdateApplicationStagingIncludesSpaceGuidInLookupQuery() { + UUID spaceGuid = UUID.randomUUID(); + when(target.getGuid()) + .thenReturn(spaceGuid); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&space_guids=" + + spaceGuid + "&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getProcessJson()); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + PROCESS_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + Staging staging = ImmutableStaging.builder() + .command("start-cmd") + .build(); + + operations.updateApplicationStaging("my-app", staging); + + factory.verify(); + } + + @Test + void testUpdateApplicationStagingThrowsWhenApplicationNotFound() { + when(target.getGuid()) + .thenReturn(null); + + factory.stubGet(MockControllerClientFactory.BASE_URL + + "/v3/apps?per_page=50&names=missing-app", "{\"resources\":[]}"); + + Staging staging = ImmutableStaging.builder() + .command("cmd") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.updateApplicationStaging("missing-app", staging)); + } + + @Test + void testUpdateApplicationStagingWithCnbLifecycleWithoutBuildpacksThrows() { + when(target.getGuid()) + .thenReturn(null); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=my-app", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + + Staging staging = ImmutableStaging.builder() + .command("cmd") + .lifecycleType(LifecycleType.CNB) + .build(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> operations.updateApplicationStaging("my-app", staging)); + } + + private static String getProcessJson() { + return "{\"guid\":\"" + PROCESS_GUID + "\",\"command\":\"start-cmd\",\"instances\":2,\"memory_in_mb\":256,\"disk_in_mb\":1024," + + "\"health_check\":{\"type\":\"http\",\"data\":{\"timeout\":60,\"invocation_timeout\":5,\"endpoint\":\"/health\"," + + "\"interval\":10}}}"; + } + + private static String getStatsJson() { + return "{\"resources\":[{\"index\":0,\"state\":\"RUNNING\",\"routable\":\"true\"}," + + "{\"index\":1,\"state\":\"CRASHED\",\"routable\":\"false\"}]}"; + } + + private static String getDropletJson(String packageHref) { + return "{\"guid\":\"" + DROPLET_GUID + "\",\"links\":{\"package\":{\"href\":\"" + packageHref + "\"}}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java new file mode 100644 index 0000000000..d9c3fcdc1b --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java @@ -0,0 +1,110 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.springframework.http.HttpStatus; + +class ReactiveFanOutTest { + + @Test + void testMapConcurrentlyReturnsEmptyWhenEmpty() { + Assertions.assertEquals(List.of(), ReactiveFanOut.mapConcurrently(List.of(), x -> x)); + } + + @Test + void testMapConcurrentlyShortCircuitsWhenSingleElement() { + List result = ReactiveFanOut.mapConcurrently(List.of("a"), s -> s + "!"); + + Assertions.assertEquals(List.of("a!"), result); + } + + @Test + @Timeout(10) + void testMapConcurrentlyResultPreserveInputOrderEvenWhenTasksFinishOutOfOrder() { + List input = IntStream.rangeClosed(1, 20) + .boxed() + .toList(); + CountDownLatch secondItemDone = new CountDownLatch(1); + + List result = ReactiveFanOut.mapConcurrently(input, i -> { + if (i == 2) { + secondItemDone.countDown(); + } else if (i == 1) { + try { + secondItemDone.await(); + } catch (InterruptedException e) { + Thread.currentThread() + .interrupt(); + throw new IllegalStateException(e); + } + } + return i * 10; + }); + + List expected = input.stream() + .map(i -> i * 10) + .toList(); + Assertions.assertEquals(expected, result); + } + + @Test + @Timeout(10) + void testMapConcurrentlyWorkRunsConcurrently() { + int n = 8; + CyclicBarrier barrier = new CyclicBarrier(n); + List input = IntStream.range(0, n) + .boxed() + .toList(); + + List result = ReactiveFanOut.mapConcurrently(input, i -> { + try { + barrier.await(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + return i; + }); + + Assertions.assertEquals(input, result); + } + + @Test + void testMapConcurrentlyPropagatesUnwrappedOnFirstFailure() { + List input = List.of(1, 2, 3, 4); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, + () -> ReactiveFanOut.mapConcurrently(input, i -> { + if (i == 3) { + throw new CloudOperationException(HttpStatus.BAD_GATEWAY); + } + return i; + })); + + Assertions.assertEquals(HttpStatus.BAD_GATEWAY, thrown.getStatusCode()); + } + + @Test + void testMapConcurrentlyIsInvokedOncePerItem() { + List input = IntStream.range(0, 50) + .boxed() + .toList(); + AtomicInteger invocations = new AtomicInteger(); + + List result = ReactiveFanOut.mapConcurrently(input, i -> { + invocations.incrementAndGet(); + return i; + }); + + Assertions.assertEquals(input, result); + Assertions.assertEquals(input.size(), invocations.get()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java new file mode 100644 index 0000000000..345c31b01c --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java @@ -0,0 +1,98 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.Set; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class RolesV3OperationsTest { + + private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); + private static final UUID USER_GUID = UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + private static final String ROLES_URI = "/v3/roles?per_page=50&space_guids=" + SPACE_GUID + "&user_guids=" + USER_GUID; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private RolesV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new RolesV3Operations(factory.client()); + } + + @Test + void testGetUserRolesReturnsMappedRoles() { + String json = "{\"resources\":[{\"guid\":\"r1\",\"type\":\"space_developer\"}," + + "{\"guid\":\"r2\",\"type\":\"space_manager\"}]}"; + + factory.stubGet(MockControllerClientFactory.BASE_URL + ROLES_URI, json); + + Set result = operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID); + + Assertions.assertEquals(2, result.size()); + Assertions.assertTrue(result.contains(UserRole.SPACE_DEVELOPER)); + Assertions.assertTrue(result.contains(UserRole.SPACE_MANAGER)); + factory.verify(); + } + + @Test + void testGetUserRolesDeduplicatesRepeatedRoleTypes() { + String json = "{\"resources\":[{\"guid\":\"r1\",\"type\":\"space_developer\"}," + + "{\"guid\":\"r2\",\"type\":\"space_developer\"}]}"; + + factory.stubGet(MockControllerClientFactory.BASE_URL + ROLES_URI, json); + + Set result = operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID); + + Assertions.assertEquals(1, result.size()); + Assertions.assertTrue(result.contains(UserRole.SPACE_DEVELOPER)); + factory.verify(); + } + + @Test + void testGetUserRolesReturnsEmptySetWhenNoRoles() { + factory.stubGet(MockControllerClientFactory.BASE_URL + ROLES_URI, "{\"resources\":[]}"); + + Set result = operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetUserRolesThrowsOnUnknownRoleType() { + String json = "{\"resources\":[{\"guid\":\"r1\",\"type\":\"galactic_overlord\"}]}"; + factory.stubGet(MockControllerClientFactory.BASE_URL + ROLES_URI, json); + + Assertions.assertThrows(IllegalArgumentException.class, + () -> operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID)); + } + + @Test + void testGetUserRolesThrowsOnServerError() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.INTERNAL_SERVER_ERROR)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID)); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java new file mode 100644 index 0000000000..d0df606a2a --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java @@ -0,0 +1,336 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import static org.mockito.Mockito.when; + +class RoutesV3OperationsTest { + + private static final String SPACE_GUID = "11111111-1111-1111-1111-111111111111"; + private static final String DOMAIN_GUID = "22222222-2222-2222-2222-222222222222"; + private static final String ROUTE_GUID = "33333333-3333-3333-3333-333333333333"; + private static final String SECOND_ROUTE_GUID = "77777777-7777-7777-7777-777777777777"; + private static final String APP_GUID = "44444444-4444-4444-4444-444444444444"; + private static final String DESTINATION_GUID = "55555555-5555-5555-5555-555555555555"; + private static final String JOB_GUID = "66666666-6666-6666-6666-666666666666"; + private static final String DOMAIN_NAME = "example.com"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private RoutesV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new RoutesV3Operations(factory.client(), target); + } + + @Test + void testAddRoutePostsToRoutes() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + String route = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(route, MediaType.APPLICATION_JSON)); + + operations.addRoute("myhost", DOMAIN_NAME, "/mypath"); + + factory.verify(); + } + + @Test + void testAddRouteThrowsWhenDomainNotFound() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + stubEmptyDomainLookup(DOMAIN_NAME); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.addRoute("myhost", DOMAIN_NAME, "/mypath")); + } + + @Test + void testAddRouteThrowsWhenSpaceNotProvided() { + RoutesV3Operations noSpaceOperations = new RoutesV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.addRoute("myhost", DOMAIN_NAME, "/mypath")); + } + + @Test + void testDeleteRouteDeletesFoundRoute() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + stubRouteLookupWithResult(); + stubAsyncDelete(MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID); + + operations.deleteRoute("myhost", DOMAIN_NAME, "/mypath"); + + factory.verify(); + } + + @Test + void testDeleteRouteThrowsWhenRouteNotFound() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + factory.server() + .expect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.deleteRoute("myhost", DOMAIN_NAME, "/mypath")); + } + + @Test + void testDeleteRouteThrowsWhenSpaceNotProvided() { + RoutesV3Operations noSpaceOperations = new RoutesV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.deleteRoute("myhost", DOMAIN_NAME, "/mypath")); + } + + @Test + void testDeleteOrphanedRoutes() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubAsyncDelete(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID + "/routes?unmapped=true"); + + operations.deleteOrphanedRoutes(); + + factory.verify(); + } + + @Test + void testGetRoutesReturnsMappedRoutes() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + String firstRoute = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID, "{\"resources\":[" + firstRoute + "]}"); + + List routes = operations.getRoutes(DOMAIN_NAME); + + Assertions.assertEquals(1, routes.size()); + Assertions.assertEquals("myhost", routes.getFirst() + .getHost()); + factory.verify(); + } + + @Test + void testGetRoutesReturnsMultipleMappedRoutes() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + String firstRoute = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + String secondRoute = getRouteJson(SECOND_ROUTE_GUID, "otherhost", "/otherpath", "otherhost.example.com/otherpath"); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID, "{\"resources\":[" + firstRoute + "," + secondRoute + "]}"); + + List routes = operations.getRoutes(DOMAIN_NAME); + + Assertions.assertEquals(2, routes.size()); + Assertions.assertEquals(Set.of("myhost", "otherhost"), routes.stream() + .map(CloudRoute::getHost) + .collect(Collectors.toSet())); + Assertions.assertEquals(Set.of("myhost.example.com/mypath", "otherhost.example.com/otherpath"), + routes.stream() + .map(CloudRoute::getUrl) + .collect(Collectors.toSet())); + factory.verify(); + } + + @Test + void testGetRoutesThrowsWhenDomainNotFound() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubEmptyDomainLookup(DOMAIN_NAME); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.getRoutes(DOMAIN_NAME)); + } + + @Test + void testGetRoutesThrowsWhenSpaceNotProvided() { + RoutesV3Operations noSpaceOperations = new RoutesV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.getRoutes(DOMAIN_NAME)); + } + + @Test + void testGetApplicationRoutesReturnsMappedRoutes() { + String route = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", + "{\"resources\":[" + route + "]}"); + + List routes = operations.getApplicationRoutes(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, routes.size()); + Assertions.assertEquals("myhost.example.com/mypath", routes.getFirst() + .getUrl()); + factory.verify(); + } + + @Test + void testGetApplicationRoutesReturnsEmptyList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", + "{\"resources\":[]}"); + + List routes = operations.getApplicationRoutes(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(0, routes.size()); + factory.verify(); + } + + @Test + void testUpdateApplicationRoutesAddsNewRoute() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + String route = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + + stubAppLookup("my-app"); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", + "{\"resources\":[]}"); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", + getDomainJson(DOMAIN_GUID)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", "{\"resources\":[]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(route, MediaType.APPLICATION_JSON)); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations", HttpMethod.POST); + + CloudRoute newRoute = ImmutableCloudRoute.builder() + .domain(ImmutableCloudDomain.builder() + .name(DOMAIN_NAME) + .build()) + .host("myhost") + .path("/mypath") + .url("myhost.example.com/mypath") + .build(); + + operations.updateApplicationRoutes("my-app", Set.of(newRoute)); + + factory.verify(); + } + + @Test + void testUpdateApplicationRoutesRemovesOutdatedRoute() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubAppLookup("my-app"); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", + "{\"resources\":[" + getRouteWithDestinationJson() + "]}"); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations/" + DESTINATION_GUID, + HttpMethod.DELETE); + + operations.updateApplicationRoutes("my-app", Set.of()); + + factory.verify(); + } + + @Test + void testUpdateApplicationRoutesThrowsWhenApplicationNotFound() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50" + + "&space_guids=" + SPACE_GUID + "&names=missing-app", "{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.updateApplicationRoutes("missing-app", Set.of())); + } + + private void stubDomainLookup(String name, String domainGuid) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=50", + getDomainJson(domainGuid)); + } + + private void stubEmptyDomainLookup(String name) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=50", + "{\"resources\":[]}"); + } + + private void stubRouteLookupWithResult() { + String route = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", + "{\"resources\":[" + route + "]}"); + } + + private void stubAppLookup(String appName) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50" + + "&space_guids=" + SPACE_GUID + "&names=" + appName, "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + } + + private void stubAsyncDelete(String uri) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID, + "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}"); + } + + private static String getRouteJson(String guid, String host, String path, String url) { + return "{\"guid\":\"" + guid + "\",\"host\":\"" + host + "\",\"path\":\"" + path + "\",\"url\":\"" + url + "\"," + + "\"destinations\":[],\"relationships\":{\"space\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}," + + "\"domain\":{\"data\":{\"guid\":\"" + DOMAIN_GUID + "\"}}}}"; + } + + private static String getRouteWithDestinationJson() { + return "{\"guid\":\"" + ROUTE_GUID + "\",\"host\":\"myhost\",\"path\":\"/mypath\",\"url\":\"myhost.example.com/mypath\"," + + "\"destinations\":[{\"guid\":\"" + DESTINATION_GUID + "\",\"app\":{\"guid\":\"" + APP_GUID + "\"},\"protocol\":\"http1\"}]," + + "\"relationships\":{\"space\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}," + + "\"domain\":{\"data\":{\"guid\":\"" + DOMAIN_GUID + "\"}}}}"; + } + + private static String getDomainJson(String domainGuid) { + return "{\"resources\":[{\"guid\":\"" + domainGuid + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java new file mode 100644 index 0000000000..a1d63faefa --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java @@ -0,0 +1,370 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import static org.mockito.Mockito.when; + +class ServiceBindingsV3OperationsTest { + + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String SERVICE_INSTANCE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String BINDING_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String JOB_GUID = "99999999-8888-7777-6666-555555555555"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private ServiceBindingsV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ServiceBindingsV3Operations(factory.client(), target); + } + + @Test + void testBindServiceInstanceReturnsJobGuidFromLocation() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookup("my-app"); + stubServiceInstanceLookup("my-service"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + Optional jobId = operations.bindServiceInstance("my-binding", "my-app", "my-service"); + + Assertions.assertTrue(jobId.isPresent()); + Assertions.assertEquals(JOB_GUID, jobId.get()); + factory.verify(); + } + + @Test + void testBindServiceInstanceWithParametersReturnsEmptyWhenNoLocation() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookup("my-app"); + stubServiceInstanceLookup("my-service"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.CREATED)); + + Optional jobId = operations.bindServiceInstance("my-binding", "my-app", "my-service", Map.of("key", "value")); + + Assertions.assertTrue(jobId.isEmpty()); + factory.verify(); + } + + @Test + void testBindServiceInstanceUsesSpaceGuidInLookupWhenTargetHasGuid() { + UUID spaceGuid = UUID.randomUUID(); + when(target.getGuid()) + .thenReturn(spaceGuid); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&space_guids=" + + spaceGuid + "&names=my-app")) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(APP_GUID), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_instances?per_page=50&space_guids=" + spaceGuid + + "&names=my-service")) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(SERVICE_INSTANCE_GUID), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.CREATED)); + + Optional jobId = operations.bindServiceInstance("my-binding", "my-app", "my-service"); + + Assertions.assertTrue(jobId.isEmpty()); + factory.verify(); + } + + @Test + void testBindServiceInstanceThrowsWhenApplicationNotFound() { + when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=missing-app")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.bindServiceInstance("my-binding", "missing-app", "my-service")); + } + + @Test + void testBindServiceInstanceThrowsWhenServiceInstanceNotFound() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookup("my-app"); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_instances?per_page=50&names=missing-service")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.bindServiceInstance("my-binding", "my-app", "missing-service")); + } + + @Test + void testUnbindServiceInstanceByNameDeletesFoundBindings() { + when(target.getGuid()) + .thenReturn(null); + + stubAppLookup("my-app"); + stubServiceInstanceLookup("my-service"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + + "&per_page=50")) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(BINDING_GUID), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + List jobIds = operations.unbindServiceInstance("my-app", "my-service"); + + Assertions.assertEquals(List.of(JOB_GUID), jobIds); + factory.verify(); + } + + @Test + void testUnbindServiceInstanceByGuidDeletesFoundBindings() { + UUID appGuid = UUID.fromString(APP_GUID); + UUID serviceInstanceGuid = UUID.fromString(SERVICE_INSTANCE_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + + "&per_page=50")) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(BINDING_GUID), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NO_CONTENT)); + + List jobIds = operations.unbindServiceInstance(appGuid, serviceInstanceGuid); + + Assertions.assertTrue(jobIds.isEmpty()); + factory.verify(); + } + + @Test + void testUnbindServiceInstanceThrowsWhenNoBindingFound() { + UUID appGuid = UUID.fromString(APP_GUID); + UUID serviceInstanceGuid = UUID.fromString(SERVICE_INSTANCE_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + + "&per_page=50")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.unbindServiceInstance(appGuid, serviceInstanceGuid)); + } + + @Test + void testDeleteServiceBindingReturnsJobGuid() { + UUID bindingGuid = UUID.fromString(BINDING_GUID); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + Optional jobId = operations.deleteServiceBinding(bindingGuid); + + Assertions.assertTrue(jobId.isPresent()); + Assertions.assertEquals(JOB_GUID, jobId.get()); + factory.verify(); + } + + @Test + void testGetServiceBindingReturnsMappedBinding() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" + + BINDING_GUID + "&per_page=50", getBindingListJson()); + + CloudServiceBinding result = operations.getServiceBinding(UUID.fromString(BINDING_GUID)); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result.getApplicationGuid()); + Assertions.assertEquals(UUID.fromString(SERVICE_INSTANCE_GUID), result.getServiceInstanceGuid()); + factory.verify(); + } + + @Test + void testGetServiceBindingReturnsNullWhenNoBindingFound() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" + + BINDING_GUID + "&per_page=50", "{\"resources\":[]}"); + + CloudServiceBinding result = operations.getServiceBinding(UUID.fromString(BINDING_GUID)); + + Assertions.assertNull(result); + factory.verify(); + } + + @Test + void testGetServiceAppBindingsReturnsMappedList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?service_instance_guids=" + + SERVICE_INSTANCE_GUID + "&type=app&per_page=50", getBindingListJson()); + + List result = operations.getServiceAppBindings(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(UUID.fromString(SERVICE_INSTANCE_GUID), result.getFirst() + .getServiceInstanceGuid()); + factory.verify(); + } + + @Test + void testGetAppBindingsReturnsEmptyList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&per_page=50", "{\"resources\":[]}"); + + List result = operations.getAppBindings(UUID.fromString(APP_GUID)); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetServiceBindingsForApplicationReturnsMappedList() { + factory.stubGet(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + + "&per_page=50", getBindingListJson()); + + List result = operations.getServiceBindingsForApplication(UUID.fromString(APP_GUID), + UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceBindingParametersReturnsMap() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID + "/parameters", "{\"foo\":\"bar\"}"); + + Map result = operations.getServiceBindingParameters(UUID.fromString(BINDING_GUID)); + + Assertions.assertEquals("bar", result.get("foo")); + factory.verify(); + } + + @Test + void testGetServiceBindingParametersThrowsWhenNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID + "/parameters")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + UUID bindingGuid = UUID.fromString(BINDING_GUID); + Assertions.assertThrows(CloudOperationException.class, + () -> operations.getServiceBindingParameters(bindingGuid)); + } + + @Test + void testUpdateServiceBindingMetadataUpdates() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + BINDING_GUID + "\"}", MediaType.APPLICATION_JSON)); + + Metadata metadata = ImmutableMetadata.builder() + .putLabel("key", "value") + .build(); + + Assertions.assertDoesNotThrow(() -> operations.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), metadata)); + factory.verify(); + } + + @Test + void testUpdateServiceBindingMetadataThrowsOnError() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.UNPROCESSABLE_ENTITY)); + + Metadata metadata = ImmutableMetadata.builder() + .build(); + + UUID bindingGuid = UUID.fromString(BINDING_GUID); + Assertions.assertThrows(CloudOperationException.class, + () -> operations.updateServiceBindingMetadata(bindingGuid, metadata)); + } + + private void stubAppLookup(String appName) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=" + appName)) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(APP_GUID), MediaType.APPLICATION_JSON)); + } + + private void stubServiceInstanceLookup(String serviceName) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50&names=" + + serviceName)) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(SERVICE_INSTANCE_GUID), MediaType.APPLICATION_JSON)); + } + + private static String getResourcesJson(String guid) { + return "{\"resources\":[{\"guid\":\"" + guid + "\"}]}"; + } + + private static String getBindingListJson() { + return "{\"resources\":[{\"guid\":\"" + BINDING_GUID + "\",\"name\":\"my-binding\",\"type\":\"app\"," + + "\"relationships\":{\"app\":{\"data\":{\"guid\":\"" + APP_GUID + "\"}}," + + "\"service_instance\":{\"data\":{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\"}}}}]}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java new file mode 100644 index 0000000000..83fb192b09 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java @@ -0,0 +1,277 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import static org.mockito.Mockito.when; + +class ServiceBrokersV3OperationsTest { + + private static final String BROKER_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String SPACE_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String OFFERING_GUID = "66666666-7777-8888-9999-000000000000"; + private static final String PLAN_GUID = "abcabcab-cdcd-efef-1212-343434343434"; + private static final String JOB_GUID = "99999999-8888-7777-6666-555555555555"; + + private static final String BROKERS_URI = MockControllerClientFactory.BASE_URL + "/v3/service_brokers"; + + @Mock + private CloudSpace target; + @Mock + private CloudMetadata targetMetadata; + + private MockControllerClientFactory factory; + private ServiceBrokersV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ServiceBrokersV3Operations(factory.client(), target); + } + + @Test + void testGetServiceBrokersReturnsMappedList() { + factory.stubGet(BROKERS_URI + "?per_page=50", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + List result = operations.getServiceBrokers(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("broker-a", result.getFirst() + .getName()); + Assertions.assertEquals("https://broker-a.example.com", result.getFirst() + .getUrl()); + factory.verify(); + } + + @Test + void testGetServiceBrokersReturnsEmptyList() { + factory.stubGet(BROKERS_URI + "?per_page=50", "{\"resources\":[]}"); + + List result = operations.getServiceBrokers(); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetServiceBrokerReturnsMappedBroker() { + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + CloudServiceBroker result = operations.getServiceBroker("broker-a"); + + Assertions.assertEquals("broker-a", result.getName()); + Assertions.assertEquals(UUID.fromString(BROKER_GUID), result.getMetadata() + .getGuid()); + factory.verify(); + } + + @Test + void testGetServiceBrokerThrowsWhenRequiredAndMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.getServiceBroker("missing")); + factory.verify(); + } + + @Test + void testGetServiceBrokerReturnsNullWhenNotRequiredAndMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + Assertions.assertNull(operations.getServiceBroker("missing", false)); + factory.verify(); + } + + @Test + void testCreateServiceBrokerReturnsJobGuidFromLocation() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("broker-a") + .url("https://broker-a.example.com") + .username("user") + .password("pass") + .spaceGuid(SPACE_GUID) + .build(); + + String jobGuid = operations.createServiceBroker(broker); + + Assertions.assertEquals(JOB_GUID, jobGuid); + factory.verify(); + } + + @Test + void testCreateServiceBrokerReturnsNullWhenNoLocation() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.CREATED)); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("broker-a") + .url("https://broker-a.example.com") + .build(); + + Assertions.assertNull(operations.createServiceBroker(broker)); + factory.verify(); + } + + @Test + void testCreateServiceBrokerThrowsWhenNull() { + Assertions.assertThrows(IllegalArgumentException.class, () -> operations.createServiceBroker(null)); + } + + @Test + void testDeleteServiceBrokerLooksUpThenDeletesAndReturnsJobGuid() { + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "/" + BROKER_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + String jobGuid = operations.deleteServiceBroker("broker-a"); + + Assertions.assertEquals(JOB_GUID, jobGuid); + factory.verify(); + } + + @Test + void testDeleteServiceBrokerThrowsWhenBrokerMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.deleteServiceBroker("missing")); + factory.verify(); + } + + @Test + void testUpdateServiceBrokerLooksUpThenUpdatesAndReturnsJobGuid() { + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "/" + BROKER_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("broker-a") + .url("https://broker-a-new.example.com") + .username("user") + .password("pass") + .build(); + + String jobGuid = operations.updateServiceBroker(broker); + + Assertions.assertEquals(JOB_GUID, jobGuid); + factory.verify(); + } + + @Test + void testUpdateServiceBrokerThrowsWhenNull() { + Assertions.assertThrows(IllegalArgumentException.class, () -> operations.updateServiceBroker(null)); + } + + @Test + void testUpdateServiceBrokerThrowsWhenBrokerMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("missing") + .url("https://missing.example.com") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.updateServiceBroker(broker)); + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerUpdatesEachPlan() { + when(target.getMetadata()) + .thenReturn(targetMetadata); + + when(targetMetadata.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=50", + "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + + OFFERING_GUID + "&per_page=50", + "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID + + "/visibility")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{\"type\":\"public\"}", MediaType.APPLICATION_JSON)); + + operations.updateServicePlanVisibilityForBroker("broker-a", ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerWithNoOfferingsUpdatesNothing() { + when(target.getMetadata()) + .thenReturn(targetMetadata); + + when(targetMetadata.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=50", "{\"resources\":[]}"); + + operations.updateServicePlanVisibilityForBroker("broker-a", ServicePlanVisibility.ADMIN); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerThrowsWhenBrokerMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.updateServicePlanVisibilityForBroker("missing", ServicePlanVisibility.PUBLIC)); + factory.verify(); + } + + private void stubBrokerLookup(String name, String responseJson) { + factory.stubGet(BROKERS_URI + "?names=" + name + "&per_page=50", responseJson); + } + + private static String getBrokerJson(String name) { + return "{\"guid\":\"" + BROKER_GUID + "\",\"name\":\"" + name + "\",\"url\":\"https://" + name + + ".example.com\",\"relationships\":{\"space\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java new file mode 100644 index 0000000000..f88f8a8cef --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java @@ -0,0 +1,438 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import static org.mockito.Mockito.when; + +class ServiceInstancesV3OperationsTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); + private static final String OFFERING_GUID = "aaaaaaaa-1111-1111-1111-aaaaaaaaaaaa"; + private static final String PLAN_GUID = "bbbbbbbb-2222-2222-2222-bbbbbbbbbbbb"; + + @Mock + private CloudSpace target; + @Mock + private CloudMetadata targetMetadata; + + private ServiceInstancesV3Operations operations; + + private MockControllerClientFactory factory; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + when(target.getMetadata()) + .thenReturn(targetMetadata); + when(targetMetadata.getGuid()) + .thenReturn(SPACE_GUID); + operations = new ServiceInstancesV3Operations(factory.client(), target); + } + + @Test + void testGetRequiredServiceInstanceGuidReturnsGuidWhenFound() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + + UUID result = operations.getRequiredServiceInstanceGuid("my-service"); + + Assertions.assertEquals(GUID_STRING, result.toString()); + } + + @Test + void testGetRequiredServiceInstanceGuidThrowsWhenNotFound() { + stubServiceInstanceLookup("missing", null); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, + () -> operations.getRequiredServiceInstanceGuid("missing")); + + Assertions.assertEquals(HttpStatus.NOT_FOUND, thrown.getStatusCode()); + } + + @Test + void testGetServiceInstanceWithoutAuxiliaryContentReturnsNullWhenNotRequiredAndMissing() { + stubServiceInstanceLookup("missing", null); + + Assertions.assertNull(operations.getServiceInstanceWithoutAuxiliaryContent("missing", false)); + } + + @Test + void testGetServiceInstanceWithoutAuxiliaryContentThrowsWhenRequiredAndMissing() { + stubServiceInstanceLookup("missing", null); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, + () -> operations.getServiceInstanceWithoutAuxiliaryContent("missing")); + + Assertions.assertEquals(HttpStatus.NOT_FOUND, thrown.getStatusCode()); + } + + @Test + void testGetServiceInstanceWithoutAuxiliaryContentMapsWhenFound() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + + CloudServiceInstance result = operations.getServiceInstanceWithoutAuxiliaryContent("my-service"); + + Assertions.assertEquals("my-service", result.getName()); + } + + @Test + void testGetServiceInstanceNameReturnsNameFromGet() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SPACE_GUID, getManagedInstanceJson("my-service")); + + Assertions.assertEquals("my-service", operations.getServiceInstanceName(SPACE_GUID)); + factory.verify(); + } + + @Test + void testGetServiceInstanceNameReturnsNullWhenGetReturnsNull() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SPACE_GUID, null); + + Assertions.assertNull(operations.getServiceInstanceName(SPACE_GUID)); + factory.verify(); + } + + @Test + void testCreateServiceInstancePostsManagedInstance() { + stubOfferingLookup("my-offering", null); + stubPlanLookup("my-plan"); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances", HttpMethod.POST); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .label("my-offering") + .plan("my-plan") + .build(); + + operations.createServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testCreateServiceInstanceThrowsWhenPlanNotFound() { + stubOfferingLookup("my-offering", null); + factory.server() + .expect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .label("my-offering") + .plan("missing-plan") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.createServiceInstance(serviceInstance)); + } + + @Test + void testCreateServiceInstanceThrowsWhenSpaceNotProvided() { + ServiceInstancesV3Operations noSpaceOperations = new ServiceInstancesV3Operations(factory.client(), null); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .label("my-offering") + .plan("my-plan") + .build(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.createServiceInstance(serviceInstance)); + } + + @Test + void testCreateUserProvidedServiceInstancePosts() { + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances", HttpMethod.POST); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-ups") + .syslogDrainUrl("syslog://example.com") + .credentials(Map.of("user", "admin")) + .build(); + + operations.createUserProvidedServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testCreateUserProvidedServiceInstanceThrowsWhenSpaceNotProvided() { + ServiceInstancesV3Operations noSpaceOperations = new ServiceInstancesV3Operations(factory.client(), null); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-ups") + .build(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.createUserProvidedServiceInstance(serviceInstance)); + } + + @Test + void testDeleteServiceInstanceByNameDeletesFoundInstance() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.DELETE); + + operations.deleteServiceInstance("my-service"); + + factory.verify(); + } + + @Test + void testDeleteServiceInstanceByNameThrowsWhenNotFound() { + stubServiceInstanceLookup("missing", null); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.deleteServiceInstance("missing")); + } + + @Test + void testDeleteServiceInstanceByInstanceDeletes() { + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.DELETE); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .metadata( + org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata.builder() + .guid( + UUID.fromString( + GUID_STRING)) + .build()) + .name("my-service") + .build(); + + operations.deleteServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testUpdateServicePlanUpdatesRelationship() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + + stubPlanForNameResolution(); + stubOfferingForNameResolution(); + + stubOfferingLookup("my-offering", null); + stubPlanLookup("new-plan"); + stubPatch(); + + operations.updateServicePlan("my-service", "new-plan"); + + factory.verify(); + } + + @Test + void testUpdateServicePlanSkipsUserProvided() { + stubServiceInstanceLookup("my-ups", getUserProvidedInstanceJson("my-ups")); + + operations.updateServicePlan("my-ups", "new-plan"); + + factory.verify(); + } + + @Test + void testUpdateServiceParametersUpdatesManaged() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + stubPatch(); + + operations.updateServiceParameters("my-service", Map.of("foo", "bar")); + + factory.verify(); + } + + @Test + void testUpdateServiceParametersUpdatesUserProvided() { + stubServiceInstanceLookup("my-ups", getUserProvidedInstanceJson("my-ups")); + stubPatch(); + + operations.updateServiceParameters("my-ups", Map.of("user", "admin")); + + factory.verify(); + } + + @Test + void testUpdateServiceTagsUpdates() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + stubPatch(); + + operations.updateServiceTags("my-service", List.of("tag1", "tag2")); + + factory.verify(); + } + + @Test + void testUpdateServiceTagsThrowsWhenNotFound() { + stubServiceInstanceLookup("missing", null); + + List tags = List.of("tag1"); + Assertions.assertThrows(CloudOperationException.class, () -> operations.updateServiceTags("missing", tags)); + } + + @Test + void testUpdateServiceSyslogDrainUrlUpdatesUserProvided() { + stubServiceInstanceLookup("my-ups", getUserProvidedInstanceJson("my-ups")); + stubPatch(); + + operations.updateServiceSyslogDrainUrl("my-ups", "syslog://new.example.com"); + + factory.verify(); + } + + @Test + void testUpdateServiceSyslogDrainUrlSkipsManaged() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + + operations.updateServiceSyslogDrainUrl("my-service", "syslog://new.example.com"); + + factory.verify(); + } + + @Test + void testUpdateServiceInstanceMetadataUpdates() { + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.PATCH); + + Metadata metadata = ImmutableMetadata.builder() + .putLabel("key", "value") + .build(); + + operations.updateServiceInstanceMetadata(UUID.fromString(GUID_STRING), metadata); + + factory.verify(); + } + + @Test + void testUpdateServiceInstanceMetadataUpdatesWithNullMetadata() { + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.PATCH); + + operations.updateServiceInstanceMetadata(UUID.fromString(GUID_STRING), null); + + factory.verify(); + } + + @Test + void testGetServiceInstancesByMetadataLabelSelectorMapsUserProvided() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + + "&space_guids=" + SPACE_GUID + "&label_selector=env", + "{\"resources\":[" + getUserProvidedInstanceJson("ups-1") + "]}"); + + List result = operations.getServiceInstancesByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("ups-1", result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetServiceInstancesByMetadataLabelSelectorReturnsEmpty() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + + "&space_guids=" + SPACE_GUID + "&label_selector=env", "{\"resources\":[]}"); + + List result = operations.getServiceInstancesByMetadataLabelSelector("env"); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelectorMaps() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + + "&space_guids=" + SPACE_GUID + "&label_selector=env", + "{\"resources\":[" + getManagedInstanceJson("mi-1") + "]}"); + + List result = operations.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("mi-1", result.getFirst() + .getName()); + Assertions.assertEquals(ServiceInstanceType.MANAGED, result.getFirst() + .getType()); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByNames() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getManagedInstanceJson("my-service") + "]}"); + + List result = operations.getServiceInstancesWithoutAuxiliaryContentByNames(List.of("my-service")); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("my-service", result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByNamesReturnsEmptyForEmptyInput() { + Assertions.assertEquals(0, operations.getServiceInstancesWithoutAuxiliaryContentByNames(List.of()) + .size()); + + factory.verify(); + } + + private void stubServiceInstanceLookup(String name, String instanceJson) { + String body = instanceJson == null ? "{\"resources\":[]}" : "{\"resources\":[" + instanceJson + "]}"; + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + + "&space_guids=" + SPACE_GUID + "&names=" + name, body); + } + + private void stubOfferingLookup(String label, String broker) { + String uri = MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=50&space_guids=" + SPACE_GUID + "&names=" + + label + (broker == null ? "" : "&service_broker_names=" + broker); + + factory.stubGet(uri, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"" + label + "\"}]}"); + } + + private void stubPlanLookup(String planName) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=50" + + "&service_offering_guids=" + OFFERING_GUID + "&names=" + planName, + "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"" + planName + "\"}]}"); + } + + private void stubPlanForNameResolution() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, + "{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\",\"relationships\":" + + "{\"service_offering\":{\"data\":{\"guid\":\"" + OFFERING_GUID + "\"}}}}"); + } + + private void stubOfferingForNameResolution() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings/" + OFFERING_GUID, + "{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}"); + } + + private void stubPatch() { + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.PATCH); + } + + private static String getManagedInstanceJson(String name) { + return "{\"guid\":\"" + GUID_STRING + "\",\"name\":\"" + name + "\",\"type\":\"managed\",\"relationships\":" + + "{\"service_plan\":{\"data\":{\"guid\":\"" + PLAN_GUID + "\"}}}}"; + } + + private static String getUserProvidedInstanceJson(String name) { + return "{\"guid\":\"" + GUID_STRING + "\",\"name\":\"" + name + "\",\"type\":\"user-provided\"}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java new file mode 100644 index 0000000000..6b14755df4 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java @@ -0,0 +1,256 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class ServiceKeysV3OperationsTest { + + private static final String SERVICE_INSTANCE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String KEY_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String SECOND_KEY_GUID = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"; + private static final String JOB_GUID = "99999999-8888-7777-6666-555555555555"; + private static final String KEY_NAME = "my-key"; + private static final String SECOND_KEY_NAME = "my-other-key"; + + private static final String LIST_KEYS_QUERY = "/v3/service_credential_bindings?per_page=50&type=key&service_instance_guids=" + + SERVICE_INSTANCE_GUID; + private static final String LOOKUP_KEY_QUERY = LIST_KEYS_QUERY + "&names=" + KEY_NAME; + + private MockControllerClientFactory factory; + private ServiceKeysV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ServiceKeysV3Operations(factory.client()); + } + + @Test + void testGetServiceKeyReturnsMappedKeyWithCredentials() { + stubKeyLookup(getServiceKeyResourcesJson()); + stubCredentials(); + + CloudServiceKey result = operations.getServiceKey(getManagedServiceInstance(), KEY_NAME); + + Assertions.assertEquals(KEY_NAME, result.getName()); + Assertions.assertEquals("secret", result.getCredentials() + .get("password")); + Assertions.assertEquals(UUID.fromString(KEY_GUID), result.getMetadata() + .getGuid()); + factory.verify(); + } + + @Test + void testGetServiceKeyReturnsNullWhenNotFound() { + stubKeyLookup("{\"resources\":[]}"); + + CloudServiceKey result = operations.getServiceKey(getManagedServiceInstance(), KEY_NAME); + + Assertions.assertNull(result); + factory.verify(); + } + + @Test + void testGetServiceKeysReturnsMappedKeysWithoutCredentials() { + stubKeyList(getServiceKeyResourcesJson()); + + List result = operations.getServiceKeys(getManagedServiceInstance()); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(KEY_NAME, result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetServiceKeysReturnsMultipleMappedKeysWithoutCredentials() { + stubKeyList("{\"resources\":[" + getServiceKeyJson(KEY_GUID, KEY_NAME) + "," + getServiceKeyJson(SECOND_KEY_GUID, SECOND_KEY_NAME) + + "]}"); + + List result = operations.getServiceKeys(getManagedServiceInstance()); + + Assertions.assertEquals(2, result.size()); + Assertions.assertEquals(Set.of(KEY_NAME, SECOND_KEY_NAME), result.stream() + .map(CloudServiceKey::getName) + .collect(Collectors.toSet())); + Assertions.assertEquals(Set.of(UUID.fromString(KEY_GUID), UUID.fromString(SECOND_KEY_GUID)), + result.stream() + .map(key -> key.getMetadata() + .getGuid()) + .collect(Collectors.toSet())); + factory.verify(); + } + + @Test + void testGetServiceKeysReturnsEmptyListWhenNoKeys() { + stubKeyList("{\"resources\":[]}"); + + List result = operations.getServiceKeys(getManagedServiceInstance()); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + @Test + void testGetServiceKeysWithCredentialsFetchesDetailsPerKey() { + stubKeyList(getServiceKeyResourcesJson()); + stubCredentials(); + + List result = operations.getServiceKeysWithCredentials(getManagedServiceInstance()); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("secret", result.getFirst() + .getCredentials() + .get("password")); + factory.verify(); + } + + @Test + void testCreateServiceKeyFromModelReturnsJobGuid() { + stubAcceptedPost(); + + Optional result = operations.createServiceKey(getServiceKeyModel(), getManagedServiceInstance()); + + Assertions.assertEquals(Optional.of(JOB_GUID), result); + factory.verify(); + } + + @Test + void testCreateServiceKeyWithParametersReturnsJobGuid() { + stubAcceptedPost(); + + Optional result = operations.createServiceKey(getManagedServiceInstance(), KEY_NAME, Map.of("k", "v")); + + Assertions.assertEquals(Optional.of(JOB_GUID), result); + factory.verify(); + } + + @Test + void testCreateServiceKeyReturnsEmptyWhenNoLocationHeader() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED)); + + Optional result = operations.createServiceKey(getManagedServiceInstance(), KEY_NAME, null); + + Assertions.assertEquals(Optional.empty(), result); + factory.verify(); + } + + @Test + void testCreateServiceKeyThrowsForUserProvidedService() { + CloudServiceInstance userProvidedServiceInstance = getUserProvidedServiceInstance(); + + Assertions.assertThrows(IllegalArgumentException.class, + () -> operations.createServiceKey(userProvidedServiceInstance, KEY_NAME, null)); + } + + @Test + void testCreateAndFetchServiceKeyFollowsJobThenReturnsMappedKey() { + stubAcceptedPost(); + stubJobComplete(); + stubKeyLookup(getServiceKeyResourcesJson()); + stubCredentials(); + + CloudServiceKey result = operations.createAndFetchServiceKey(getServiceKeyModel(), getManagedServiceInstance()); + + Assertions.assertEquals(KEY_NAME, result.getName()); + Assertions.assertEquals("secret", result.getCredentials() + .get("password")); + factory.verify(); + } + + @Test + void testCreateAndFetchServiceKeyReturnsNullWhenKeyNotFoundAfterJob() { + stubAcceptedPost(); + stubJobComplete(); + stubKeyLookup("{\"resources\":[]}"); + + CloudServiceKey result = operations.createAndFetchServiceKey(getServiceKeyModel(), getManagedServiceInstance()); + + Assertions.assertNull(result); + factory.verify(); + } + + private void stubKeyLookup(String responseJson) { + factory.stubGet(MockControllerClientFactory.BASE_URL + LOOKUP_KEY_QUERY, responseJson); + } + + private void stubKeyList(String responseJson) { + factory.stubGet(MockControllerClientFactory.BASE_URL + LIST_KEYS_QUERY, responseJson); + } + + private void stubCredentials() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + KEY_GUID + "/details", + "{\"credentials\":{\"password\":\"secret\"}}"); + } + + private void stubAcceptedPost() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(java.net.URI.create( + MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + } + + private void stubJobComplete() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID, + "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}"); + } + + private static String getServiceKeyResourcesJson() { + return "{\"resources\":[{\"guid\":\"" + KEY_GUID + "\",\"name\":\"" + KEY_NAME + "\",\"type\":\"key\"}]}"; + } + + private static String getServiceKeyJson(String guid, String name) { + return "{\"guid\":\"" + guid + "\",\"name\":\"" + name + "\",\"type\":\"key\"}"; + } + + private static CloudServiceInstance getManagedServiceInstance() { + return ImmutableCloudServiceInstance.builder() + .name("my-service") + .metadata(ImmutableCloudMetadata.of(UUID.fromString(SERVICE_INSTANCE_GUID))) + .type(ServiceInstanceType.MANAGED) + .build(); + } + + private static CloudServiceInstance getUserProvidedServiceInstance() { + return ImmutableCloudServiceInstance.builder() + .name("my-ups") + .metadata(ImmutableCloudMetadata.of(UUID.fromString(SERVICE_INSTANCE_GUID))) + .type(ServiceInstanceType.USER_PROVIDED) + .build(); + } + + private static CloudServiceKey getServiceKeyModel() { + return ImmutableCloudServiceKey.builder() + .name(KEY_NAME) + .build(); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java new file mode 100644 index 0000000000..b0d2b1d984 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java @@ -0,0 +1,107 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.core.ParameterizedTypeReference; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ServiceOfferingsV3OperationsTest { + + private static final String OFFERING_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String PLAN_GUID = "22222222-3333-4444-5555-666666666666"; + private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); + + @Mock + private CloudControllerV3Client client; + @Mock + private CloudSpace targetSpace; + + private ServiceOfferingsV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + operations = new ServiceOfferingsV3Operations(client, targetSpace); + } + + @Test + void testGetServiceOfferingsMapsOfferingWithItsPlans() { + when(client.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) + .thenReturn(List.of(getOffering())); + when(client.list(ArgumentMatchers.contains("/v3/service_plans"), + ArgumentMatchers.>> any())) + .thenReturn(List.of(getPlan("plan-a"), getPlan("plan-b"))); + + List result = operations.getServiceOfferings(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("my-offering", result.getFirst() + .getName()); + Assertions.assertEquals(2, result.getFirst() + .getServicePlans() + .size()); + } + + @Test + void testGetServiceOfferingsScopesQueryToSpaceWhenTargetHasGuid() { + when(targetSpace.getMetadata()) + .thenReturn(ImmutableCloudMetadata.of(SPACE_GUID)); + + when(client.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + operations.getServiceOfferings(); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + verify(client) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("space_guids=" + SPACE_GUID), uriCaptor.getValue()); + } + + @Test + void testGetServiceOfferingsExcludesSpaceGuidWhenTargetGuidNull() { + when(targetSpace.getGuid()) + .thenReturn(null); + + when(client.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + operations.getServiceOfferings(); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + verify(client) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + Assertions.assertFalse(uriCaptor.getValue() + .contains("space_guids="), uriCaptor.getValue()); + } + + private static V3ServiceOffering getOffering() { + return new V3ServiceOffering(OFFERING_GUID, "my-offering", "desc", true, false, null, null, null, null, null, null, null); + } + + private static V3ServicePlan getPlan(String name) { + return new V3ServicePlan(PLAN_GUID, name, null, true, "public", null, null, null, null, null, null, null, null, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java new file mode 100644 index 0000000000..467dfd49a0 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java @@ -0,0 +1,137 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import static org.mockito.Mockito.when; + +class ServicePlansV3OperationsTest { + + private static final String BROKER_GUID = "11111111-1111-1111-1111-111111111111"; + private static final String OFFERING_GUID = "22222222-2222-2222-2222-222222222222"; + private static final String PLAN_GUID = "33333333-3333-3333-3333-333333333333"; + private static final UUID SPACE_GUID = UUID.fromString("44444444-4444-4444-4444-444444444444"); + private static final String BROKER_NAME = "my-broker"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private ServicePlansV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ServicePlansV3Operations(factory.client(), target); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerUpdatesAllPlansWhenNoSpaceFilter() { + when(target.getGuid()) + .thenReturn(null); + + stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); + stubOfferingLookup(null, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + stubPlanLookup("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); + stubVisibilityPatch(PLAN_GUID); + + operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerAppendsSpaceGuidWhenTargetHasGuid() { + when(target.getMetadata()) + .thenReturn(ImmutableCloudMetadata.of(SPACE_GUID)); + stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); + stubOfferingLookup(SPACE_GUID, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + stubPlanLookup("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); + stubVisibilityPatch(PLAN_GUID); + + operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.ORGANIZATION); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerThrowsWhenBrokerNotFound() { + stubBrokerLookup("{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.PUBLIC)); + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerDoesNothingWhenNoOfferings() { + when(target.getGuid()) + .thenReturn(null); + + stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); + stubOfferingLookup(null, "{\"resources\":[]}"); + + operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerDoesNothingWhenNoPlans() { + when(target.getGuid()) + .thenReturn(null); + + stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); + stubOfferingLookup(null, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + stubPlanLookup("{\"resources\":[]}"); + + operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + private void stubBrokerLookup(String responseJson) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=" + BROKER_NAME + "&per_page=50", + responseJson); + } + + private void stubOfferingLookup(UUID spaceGuid, String responseJson) { + String uri = MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + BROKER_GUID + + "&per_page=50"; + + if (spaceGuid != null) { + uri = uri + "&space_guids=" + spaceGuid; + } + + factory.stubGet(uri, responseJson); + } + + private void stubPlanLookup(String responseJson) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + + "&per_page=50", responseJson); + } + + private void stubVisibilityPatch(String planGuid) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + planGuid + + "/visibility")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.OK)); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java new file mode 100644 index 0000000000..7b3a467bf4 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java @@ -0,0 +1,97 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Stack; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class StacksV3OperationsTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Mock + private CloudControllerV3Client client; + + private StacksV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + operations = new StacksV3Operations(client); + } + + @Test + void testGetStackReturnsMappedStackWhenFound() { + mockStackList(new V3Stack(GUID_STRING, "cflinuxfs4", "Ubuntu-based stack", null, null, null, null)); + + CloudStack result = operations.getStack("cflinuxfs4"); + + Assertions.assertEquals("cflinuxfs4", result.getName()); + Assertions.assertEquals("Ubuntu-based stack", result.getDescription()); + } + + @Test + void testGetStackRequiredThrowsWhenNotFound() { + mockStackList(); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, () -> operations.getStack("missing")); + + Assertions.assertEquals(HttpStatus.NOT_FOUND, thrown.getStatusCode()); + } + + @Test + void testGetStackNotRequiredReturnsNullWhenNotFound() { + mockStackList(); + + Assertions.assertNull(operations.getStack("missing", false)); + } + + @Test + void testGetStacksMapsAllResults() { + mockStackList(new V3Stack(GUID_STRING, "cflinuxfs4", null, null, null, null, null), + new V3Stack(GUID_STRING, "cflinuxfs3", null, null, null, null, null)); + + List result = operations.getStacks(); + + Assertions.assertEquals(2, result.size()); + Assertions.assertEquals("cflinuxfs4", result.getFirst() + .getName()); + } + + @Test + void testGetStacksQueriesStacksEndpointWithPageSize() { + mockStackList(); + + operations.getStacks(); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + verify(client) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("/v3/stacks"), uriCaptor.getValue()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("per_page="), uriCaptor.getValue()); + } + + private void mockStackList(V3Stack... stacks) { + when(client.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())) + .thenReturn(List.of(stacks)); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java new file mode 100644 index 0000000000..5d05638dc2 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java @@ -0,0 +1,116 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import static org.mockito.Mockito.when; + +class TasksV3OperationsTest { + + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String TASK_GUID = "99999999-8888-7777-6666-555555555555"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private TasksV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new TasksV3Operations(factory.client(), target); + } + + @Test + void testGetTaskReturnsMappedTask() { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID, getTaskJson("SUCCEEDED")); + + CloudTask result = operations.getTask(UUID.fromString(TASK_GUID)); + + Assertions.assertEquals("migrate", result.getName()); + Assertions.assertEquals(CloudTask.State.SUCCEEDED, result.getState()); + factory.verify(); + } + + @Test + void testRunTaskPostsBodyAndReturnsCreatedTask() { + stubAppLookup(); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/tasks")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson("RUNNING"), MediaType.APPLICATION_JSON)); + + CloudTask task = ImmutableCloudTask.builder() + .name("migrate") + .command("rails db:migrate") + .build(); + + CloudTask result = operations.runTask("my-app", task); + + Assertions.assertEquals(CloudTask.State.RUNNING, result.getState()); + factory.verify(); + } + + @Test + void testCancelTaskPostsToCancelAction() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID + + "/actions/cancel")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson("CANCELING"), MediaType.APPLICATION_JSON)); + + CloudTask result = operations.cancelTask(UUID.fromString(TASK_GUID)); + + Assertions.assertEquals(CloudTask.State.CANCELING, result.getState()); + factory.verify(); + } + + @Test + void testRunTaskThrowsWhenApplicationNotFound() { + when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=missing-app")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + CloudTask task = ImmutableCloudTask.builder() + .name("migrate") + .command("cmd") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.runTask("missing-app", task)); + } + + private void stubAppLookup() { + when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=my-app")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", + MediaType.APPLICATION_JSON)); + } + + private static String getTaskJson(String state) { + return "{\"guid\":\"" + TASK_GUID + "\",\"name\":\"migrate\",\"command\":\"rails db:migrate\",\"state\":\"" + state + + "\",\"memory_in_mb\":256,\"disk_in_mb\":1024}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3UriTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3UriTest.java new file mode 100644 index 0000000000..1c8c8c4af8 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3UriTest.java @@ -0,0 +1,149 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3UriTest { + + @Test + void testPathWithoutPlaceholdersOrQueryReturnsPathAsIs() { + Assertions.assertEquals("/v3/domains", V3Uri.path("/v3/domains") + .build()); + } + + @Test + void testPathFillsSinglePlaceholder() { + Assertions.assertEquals("/v3/jobs/job-guid", V3Uri.path("/v3/jobs/{guid}", "job-guid") + .build()); + } + + @Test + void testPathFillsMultiplePlaceholdersInOrder() { + Assertions.assertEquals("/v3/apps/app-guid/features/ssh", V3Uri.path("/v3/apps/{guid}/features/{name}", "app-guid", "ssh") + .build()); + } + + @Test + void testPathFillsPlaceholdersInsideAndQueryAfter() { + Assertions.assertEquals("/v3/service_instances/si-guid/parameters", + V3Uri.path("/v3/service_instances/{guid}/parameters", "si-guid") + .build()); + } + + @Test + void testPerPageAddedAsFirstQueryParameter() { + Assertions.assertEquals("/v3/domains?per_page=5000", V3Uri.path("/v3/domains") + .perPage(5000) + .build()); + } + + @Test + void testMultipleQueryParametersUseAmpersandSeparators() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("space_guids", "space-guid") + .query("type", "key") + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000&space_guids=space-guid&type=key", uri); + } + + @Test + void testNullQueryValueIsSkipped() { + String uri = V3Uri.path("/v3/apps") + .perPage(5000) + .query("names", (Object) null) + .build(); + + Assertions.assertEquals("/v3/apps?per_page=5000", uri); + } + + @Test + void testCollectionQueryValueIsCommaJoined() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("names", List.of("a", "b", "c")) + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000&names=a,b,c", uri); + } + + @Test + void testCollectionQueryEncodesCommaWithinAnElement() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("names", List.of("a,b", "c")) + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000&names=a%2Cb,c", uri); + } + + @Test + void testQueryEncodedEscapesCommaInSingleValue() { + String uri = V3Uri.path("/v3/apps") + .perPage(5000) + .queryEncoded("names", "my,app") + .build(); + + Assertions.assertEquals("/v3/apps?per_page=5000&names=my%2Capp", uri); + } + + @Test + void testQueryEncodedLeavesCommaFreeValueUnchanged() { + String uri = V3Uri.path("/v3/apps") + .queryEncoded("names", "my-app") + .build(); + + Assertions.assertEquals("/v3/apps?names=my-app", uri); + } + + @Test + void testQueryEncodedSkipsNullValue() { + String uri = V3Uri.path("/v3/apps") + .perPage(5000) + .queryEncoded("names", null) + .build(); + + Assertions.assertEquals("/v3/apps?per_page=5000", uri); + } + + @Test + void testNullCollectionQueryValueIsSkipped() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("names", null) + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000", uri); + } + + @Test + void testEmptyCollectionQueryValueIsSkipped() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("names", List.of()) + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000", uri); + } + + @Test + void testPlaceholderValueContainingDollarAndBackslashIsNotInterpretedAsRegex() { + String uri = V3Uri.path("/v3/apps/{guid}", "weird$name\\value") + .build(); + + Assertions.assertEquals("/v3/apps/weird$name\\value", uri); + } + + @Test + void testQueryValueContainingSpecialCharsIsKeptRaw() { + String uri = V3Uri.path("/v3/apps") + .query("label_selector", "a=b,c in (d)") + .build(); + + Assertions.assertEquals("/v3/apps?label_selector=a=b,c in (d)", uri); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java new file mode 100644 index 0000000000..1686dfae6f --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java @@ -0,0 +1,91 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application.V3Lifecycle; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application.V3LifecycleData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ApplicationMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudApplicationMapsAllFields() { + V3Lifecycle lifecycle = new V3Lifecycle("buildpack", new V3LifecycleData(List.of("java_buildpack"), "cflinuxfs4")); + V3Application app = application("STARTED", lifecycle); + + CloudApplication result = V3ApplicationMapper.toCloudApplication(app, null); + + Assertions.assertEquals("my-app", result.getName()); + Assertions.assertEquals(CloudApplication.State.STARTED, result.getState()); + Assertions.assertEquals(LifecycleType.BUILDPACK, result.getLifecycle() + .getType()); + Assertions.assertEquals(Map.of("buildpacks", List.of("java_buildpack"), "stack", "cflinuxfs4"), result.getLifecycle() + .getData()); + } + + @Test + void testToCloudApplicationNullStateReturnsNullState() { + V3Application app = application(null, null); + + Assertions.assertNull(V3ApplicationMapper.toCloudApplication(app, null) + .getState()); + } + + @Test + void testToCloudApplicationNullLifecycleReturnsNullLifecycle() { + V3Application app = application("STOPPED", null); + + Assertions.assertNull(V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle()); + } + + @Test + void testToCloudApplicationLifecycleWithNullTypeReturnsNullLifecycle() { + V3Lifecycle lifecycle = new V3Lifecycle(null, new V3LifecycleData(List.of(), "cflinuxfs4")); + V3Application app = application("STARTED", lifecycle); + + Assertions.assertNull(V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle()); + } + + @Test + void testToCloudApplicationLifecycleTypeIsUpperCased() { + V3Lifecycle lifecycle = new V3Lifecycle("docker", null); + V3Application app = application("STARTED", lifecycle); + + Assertions.assertEquals(LifecycleType.DOCKER, V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle() + .getType()); + } + + @Test + void testToCloudApplicationNullLifecycleDataReturnsEmptyDataMap() { + V3Lifecycle lifecycle = new V3Lifecycle("buildpack", null); + V3Application app = application("STARTED", lifecycle); + + Assertions.assertEquals(Map.of(), V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle() + .getData()); + } + + @Test + void testToCloudApplicationLifecycleDataWithNullFieldsReturnsDefaultedMap() { + V3Lifecycle lifecycle = new V3Lifecycle("buildpack", new V3LifecycleData(null, null)); + V3Application app = application("STARTED", lifecycle); + + Assertions.assertEquals(Map.of("buildpacks", List.of(), "stack", ""), V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle() + .getData()); + } + + private V3Application application(String state, V3Lifecycle lifecycle) { + return new V3Application(GUID_STRING, "my-app", state, null, null, lifecycle, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapperTest.java new file mode 100644 index 0000000000..14098b42ca --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapperTest.java @@ -0,0 +1,60 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEvent.V3Participant; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3AuditEventMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String ACTOR_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String TARGET_GUID = "99999999-8888-7777-6666-555555555555"; + + @Test + void testToCloudEventMapsAllFields() { + V3AuditEvent event = new V3AuditEvent(GUID_STRING, "2026-08-04T10:15:30Z", null, "audit.app.update", + new V3Participant(ACTOR_GUID, "user", "admin"), + new V3Participant(TARGET_GUID, "app", "my-app")); + + CloudEvent result = V3AuditEventMapper.toCloudEvent(event); + + Assertions.assertEquals("audit.app.update", result.getType()); + Assertions.assertEquals(ACTOR_GUID, result.getActor() + .getGuid() + .toString()); + Assertions.assertEquals("admin", result.getActor() + .getName()); + Assertions.assertEquals("user", result.getActor() + .getType()); + Assertions.assertEquals("my-app", result.getTarget() + .getName()); + } + + @Test + void testToCloudEventNullActorReturnsEmptyParticipant() { + V3AuditEvent event = new V3AuditEvent(GUID_STRING, null, null, "audit.app.update", null, + new V3Participant(TARGET_GUID, "app", "my-app")); + + CloudEvent result = V3AuditEventMapper.toCloudEvent(event); + + Assertions.assertNotNull(result.getActor()); + Assertions.assertNull(result.getActor() + .getGuid()); + Assertions.assertNull(result.getActor() + .getName()); + } + + @Test + void testToCloudEventNullTargetReturnsEmptyParticipant() { + V3AuditEvent event = new V3AuditEvent(GUID_STRING, null, null, "audit.app.update", + new V3Participant(ACTOR_GUID, "user", "admin"), null); + + CloudEvent result = V3AuditEventMapper.toCloudEvent(event); + + Assertions.assertNotNull(result.getTarget()); + Assertions.assertNull(result.getTarget() + .getGuid()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java new file mode 100644 index 0000000000..4242909aff --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java @@ -0,0 +1,66 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build.V3CreatedBy; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build.V3DropletReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build.V3PackageReference; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3BuildMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String USER_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String PACKAGE_GUID = "22222222-3333-4444-5555-666666666666"; + private static final String DROPLET_GUID = "33333333-4444-5555-6666-777777777777"; + + @Test + void testToCloudBuildMapsAllFields() { + V3Build build = new V3Build(GUID_STRING, null, null, "STAGED", null, new V3CreatedBy(USER_GUID, "admin"), + new V3PackageReference(PACKAGE_GUID), new V3DropletReference(DROPLET_GUID), null, null); + + CloudBuild result = V3BuildMapper.toCloudBuild(build); + + Assertions.assertEquals(CloudBuild.State.STAGED, result.getState()); + Assertions.assertEquals(USER_GUID, result.getCreatedBy() + .getGuid() + .toString()); + Assertions.assertEquals("admin", result.getCreatedBy() + .getName()); + Assertions.assertEquals(PACKAGE_GUID, result.getPackageInfo() + .getGuid() + .toString()); + Assertions.assertEquals(DROPLET_GUID, result.getDropletInfo() + .getGuid() + .toString()); + } + + @Test + void testToCloudBuildNullCreatedByPackageAndDropletReturnNulls() { + V3Build build = new V3Build(GUID_STRING, null, null, "STAGING", "boom", null, null, null, null, null); + + CloudBuild result = V3BuildMapper.toCloudBuild(build); + + Assertions.assertNull(result.getCreatedBy()); + Assertions.assertNull(result.getPackageInfo()); + Assertions.assertNull(result.getDropletInfo()); + Assertions.assertEquals("boom", result.getError()); + } + + @Test + void testToCLoudBuildDropletWithNullGuidReturnsNullDropletInfo() { + V3Build build = new V3Build(GUID_STRING, null, null, "FAILED", null, null, null, new V3DropletReference(null), null, null); + + Assertions.assertNull(V3BuildMapper.toCloudBuild(build) + .getDropletInfo()); + } + + @Test + void testToCloudBuildNullStateReturnsNullState() { + V3Build build = new V3Build(GUID_STRING, null, null, null, null, null, null, null, null, null); + + Assertions.assertNull(V3BuildMapper.toCloudBuild(build) + .getState()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java new file mode 100644 index 0000000000..104ba0f175 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java @@ -0,0 +1,58 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3DomainRelationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3DomainMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String ORG_GUID = "11111111-2222-3333-4444-555555555555"; + + @Test + void testToCloudDomainMapsNameAndGuid() { + V3Domain domain = new V3Domain(GUID_STRING, "example.com", null, null, null, null, null); + + CloudDomain result = V3DomainMapper.toCloudDomain(domain); + + Assertions.assertEquals("example.com", result.getName()); + Assertions.assertEquals(GUID_STRING, result.getGuid() + .toString()); + } + + @Test + void testToCloudDomainWithNullGuidReturnsNullGuid() { + V3Domain domain = new V3Domain(null, "example.com", null, null, null, null, null); + + Assertions.assertNull(V3DomainMapper.toCloudDomain(domain) + .getMetadata() + .getGuid()); + } + + @Test + void testToCloudDomainIsPrivateWhenOrganizationDataPresent() { + V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(new V3RelationshipData(ORG_GUID))); + V3Domain domain = new V3Domain(GUID_STRING, "private.example.com", null, null, null, null, relationships); + + Assertions.assertTrue(domain.isPrivate()); + } + + @Test + void testToCloudDomainIsNotPrivateWhenRelationshipsNull() { + V3Domain domain = new V3Domain(GUID_STRING, "shared.example.com", null, null, null, null, null); + + Assertions.assertFalse(domain.isPrivate()); + } + + @Test + void testToCloudDomainIsNotPrivateWhenOrganizationDataNull() { + V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(null)); + V3Domain domain = new V3Domain(GUID_STRING, "shared.example.com", null, null, null, null, relationships); + + Assertions.assertFalse(domain.isPrivate()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java new file mode 100644 index 0000000000..569ea6d0ea --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java @@ -0,0 +1,74 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3InstancesInfoMapperTest { + + @Test + void testToInstancesInfoWithNullResourcesReturnsEmptyList() { + InstancesInfo result = V3InstancesInfoMapper.toInstancesInfo(new V3Process.V3ProcessStats(null)); + + Assertions.assertTrue(result.getInstances() + .isEmpty()); + } + + @Test + void testToInstancesInfoMapsEachResource() { + V3Process.V3ProcessStats stats = new V3Process.V3ProcessStats(List.of(new V3Process.V3ProcessStatsResource(0, "RUNNING", "true"), + new V3Process.V3ProcessStatsResource(1, "CRASHED", "false"))); + + InstancesInfo result = V3InstancesInfoMapper.toInstancesInfo(stats); + + Assertions.assertEquals(2, result.getInstances() + .size()); + Assertions.assertEquals(0, result.getInstances() + .getFirst() + .getIndex()); + Assertions.assertEquals(InstanceState.RUNNING, result.getInstances() + .getFirst() + .getState()); + Assertions.assertTrue(result.getInstances() + .getFirst() + .isRoutable()); + Assertions.assertEquals(InstanceState.CRASHED, result.getInstances() + .get(1) + .getState()); + Assertions.assertFalse(result.getInstances() + .get(1) + .isRoutable()); + } + + @Test + void testToInstancesInfoDefaultsUnknownStateAndNonBooleanRoutable() { + V3Process.V3ProcessStats stats = new V3Process.V3ProcessStats(List.of(new V3Process.V3ProcessStatsResource(0, "bogus", null))); + + InstancesInfo result = V3InstancesInfoMapper.toInstancesInfo(stats); + + Assertions.assertEquals(InstanceState.UNKNOWN, result.getInstances() + .getFirst() + .getState()); + Assertions.assertFalse(result.getInstances() + .getFirst() + .isRoutable()); + } + + @Test + void testToInstancesInfoWhenIndexStateAndRoutableNullDoesNotThrow() { + V3Process.V3ProcessStats stats = new V3Process.V3ProcessStats(List.of(new V3Process.V3ProcessStatsResource(null, null, null))); + + InstancesInfo result = Assertions.assertDoesNotThrow(() -> V3InstancesInfoMapper.toInstancesInfo(stats)); + + Assertions.assertEquals(0, result.getInstances() + .getFirst() + .getIndex()); + Assertions.assertFalse(result.getInstances() + .getFirst() + .isRoutable()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java new file mode 100644 index 0000000000..9b4d710a6e --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java @@ -0,0 +1,91 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3JobMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudAsyncJobMapsAllFields() { + V3Job job = new V3Job(GUID_STRING, "2026-08-04T10:15:30Z", "2026-08-05T11:16:31Z", "PROCESSING", "app.apply_manifest", + List.of(new V3Job.V3Error("Something failed", "CF-Error", 10001)), + List.of(new V3Job.V3Warning("A warning"))); + + CloudAsyncJob result = V3JobMapper.toCloudAsyncJob(job); + + Assertions.assertEquals(JobState.PROCESSING, result.getState()); + Assertions.assertEquals("app.apply_manifest", result.getOperation()); + Assertions.assertEquals("A warning", result.getWarnings()); + Assertions.assertEquals("10001 CF-Error Something failed", result.getErrors()); + Assertions.assertEquals(GUID_STRING, result.getGuid() + .toString()); + } + + @Test + void testToCloudAsyncJobParseStateIsCaseInsensitive() { + V3Job job = buildJobWithState("complete"); + + Assertions.assertEquals(JobState.COMPLETE, V3JobMapper.toCloudAsyncJob(job) + .getState()); + } + + @Test + void testToCloudAsyncJobParseStateWithNullThrows() { + V3Job job = buildJobWithState(null); + + Assertions.assertThrows(NullPointerException.class, () -> V3JobMapper.toCloudAsyncJob(job)); + } + + @Test + void testToCloudAsyncJobParseStateWithUnknownValueThrows() { + V3Job job = buildJobWithState("bogus"); + + Assertions.assertThrows(IllegalArgumentException.class, () -> V3JobMapper.toCloudAsyncJob(job)); + } + + @Test + void testToCloudAsyncJobNullWarningsReturnEmptyString() { + V3Job job = new V3Job(GUID_STRING, null, null, "FAILED", null, List.of(), null); + + Assertions.assertEquals("", V3JobMapper.toCloudAsyncJob(job) + .getWarnings()); + } + + @Test + void testToCloudAsyncJobNullErrorsReturnEmptyString() { + V3Job job = new V3Job(GUID_STRING, null, null, "FAILED", null, null, List.of()); + + Assertions.assertEquals("", V3JobMapper.toCloudAsyncJob(job) + .getErrors()); + } + + @Test + void testToCloudAsyncJobMultipleWarningsAreJoinedWithComma() { + V3Job job = new V3Job(GUID_STRING, null, null, "POLLING", null, null, + List.of(new V3Job.V3Warning("first warning"), new V3Job.V3Warning("second warning"))); + + Assertions.assertEquals("first warning,second warning", V3JobMapper.toCloudAsyncJob(job) + .getWarnings()); + } + + @Test + void testToCloudAsyncJobMultipleErrorsAreJoinedWithComma() { + V3Job job = new V3Job(GUID_STRING, null, null, "FAILED", null, + List.of(new V3Job.V3Error("detail error one", "Title1", 1), + new V3Job.V3Error("detail error two", "Title2", 2)), null); + + Assertions.assertEquals("1 Title1 detail error one,2 Title2 detail error two", V3JobMapper.toCloudAsyncJob(job) + .getErrors()); + } + + private static V3Job buildJobWithState(String state) { + return new V3Job(GUID_STRING, null, null, state, null, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponseTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponseTest.java new file mode 100644 index 0000000000..efd989cee0 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponseTest.java @@ -0,0 +1,50 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse.Link; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse.Pagination; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ListResponseTest { + + @Test + void testResourcesWithNullReturnsEmptyList() { + V3ListResponse response = new V3ListResponse<>(null, null); + + Assertions.assertTrue(response.resources() + .isEmpty()); + } + + @Test + void testResourcesReturnsSuppliedList() { + V3ListResponse response = new V3ListResponse<>(null, List.of("a", "b")); + + Assertions.assertEquals(List.of("a", "b"), response.resources()); + } + + @Test + void testNextPageHrefWithNullPaginationReturnsNull() { + V3ListResponse response = new V3ListResponse<>(null, List.of()); + + Assertions.assertNull(response.nextPageHref()); + } + + @Test + void testNextPageHrefWithNullNextLinkReturnsNull() { + Pagination pagination = new Pagination(0, 1, null, null); + V3ListResponse response = new V3ListResponse<>(pagination, List.of()); + + Assertions.assertNull(response.nextPageHref()); + } + + @Test + void testNextPageHrefReturnsHref() { + Pagination pagination = new Pagination(100, 2, new Link("/v3/apps?page=2"), null); + V3ListResponse response = new V3ListResponse<>(pagination, List.of()); + + Assertions.assertEquals("/v3/apps?page=2", response.nextPageHref()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java new file mode 100644 index 0000000000..d1b648d571 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java @@ -0,0 +1,96 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Package.V3Checksum; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Package.V3PackageData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3PackageMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudPackageMapsBitsPackage() { + V3PackageData data = new V3PackageData(new V3Checksum("sha256", "abc123"), "no error", null, null, null); + V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, data, null, null); + + CloudPackage result = V3PackageMapper.toCloudPackage(resource); + + Assertions.assertEquals(Status.READY, result.getStatus()); + Assertions.assertEquals(CloudPackage.Type.BITS, result.getType()); + BitsData bitsData = (BitsData) result.getData(); + Assertions.assertEquals("sha256", bitsData.getChecksum() + .getAlgorithm()); + Assertions.assertEquals("abc123", bitsData.getChecksum() + .getValue()); + Assertions.assertEquals("no error", bitsData.getError()); + } + + @Test + void testToCloudPackageMapsDockerPackage() { + V3PackageData data = new V3PackageData(null, null, "my/image:latest", "user", "pass"); + V3Package resource = new V3Package(GUID_STRING, "docker", "READY", null, null, data, null, null); + + CloudPackage result = V3PackageMapper.toCloudPackage(resource); + + Assertions.assertEquals(CloudPackage.Type.DOCKER, result.getType()); + DockerData dockerData = (DockerData) result.getData(); + Assertions.assertEquals("my/image:latest", dockerData.getImage()); + Assertions.assertEquals("user", dockerData.getUsername()); + } + + @Test + void testToCloudPackageNullStateReturnsNullStatus() { + V3Package resource = new V3Package(GUID_STRING, "bits", null, null, null, null, null, null); + + Assertions.assertNull(V3PackageMapper.toCloudPackage(resource) + .getStatus()); + } + + @Test + void testToCloudPackageNullTypeReturnsNullType() { + V3Package resource = new V3Package(GUID_STRING, null, "READY", null, null, null, null, null); + + Assertions.assertNull(V3PackageMapper.toCloudPackage(resource) + .getType()); + } + + @Test + void testToCloudPackageBitsPackageWithNullDataReturnsEmptyBitsData() { + V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, null, null, null); + + BitsData bitsData = (BitsData) V3PackageMapper.toCloudPackage(resource) + .getData(); + + Assertions.assertNull(bitsData.getChecksum()); + Assertions.assertNull(bitsData.getError()); + } + + @Test + void testToCloudPackageBitsPackageWithNullChecksumReturnsNullChecksum() { + V3PackageData data = new V3PackageData(null, "err", null, null, null); + V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, data, null, null); + + BitsData bitsData = (BitsData) V3PackageMapper.toCloudPackage(resource) + .getData(); + + Assertions.assertNull(bitsData.getChecksum()); + Assertions.assertEquals("err", bitsData.getError()); + } + + @Test + void testToCloudPackageDockerPackageWithNullImageDoesNotThrow() { + V3PackageData data = new V3PackageData(null, null, null, "user", "pass"); + V3Package resource = new V3Package(GUID_STRING, "docker", "READY", null, null, data, null, null); + + CloudPackage result = Assertions.assertDoesNotThrow(() -> V3PackageMapper.toCloudPackage(resource)); + + Assertions.assertEquals(CloudPackage.Type.DOCKER, result.getType()); + Assertions.assertNull(result.getData()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java new file mode 100644 index 0000000000..ad1033660c --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java @@ -0,0 +1,87 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Process.V3HealthCheck; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Process.V3HealthCheckData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ProcessMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudProcessMapsAllFields() { + V3HealthCheckData healthCheckData = new V3HealthCheckData(60, 10, "/health", 5); + V3HealthCheckData readinessData = new V3HealthCheckData(null, 8, "/ready", 3); + V3Process process = new V3Process(GUID_STRING, "start.sh", 3, 512, 2048, new V3HealthCheck("http", healthCheckData), + new V3HealthCheck("http", readinessData), null, null, null, null, null, null, null, null, + null); + + CloudProcess result = V3ProcessMapper.toCloudProcess(process); + + Assertions.assertEquals("start.sh", result.getCommand()); + Assertions.assertEquals(3, result.getInstances()); + Assertions.assertEquals(HealthCheckType.HTTP, result.getHealthCheckType()); + Assertions.assertEquals("/health", result.getHealthCheckHttpEndpoint()); + Assertions.assertEquals(60, result.getHealthCheckTimeout()); + Assertions.assertEquals("http", result.getReadinessHealthCheckType()); + Assertions.assertEquals("/ready", result.getReadinessHealthCheckHttpEndpoint()); + } + + @Test + void testToCloudProcessNullHealthCheckDefaultsToPort() { + V3Process process = new V3Process(GUID_STRING, "start.sh", 1, 256, 1024, null, null, null, null, null, null, null, null, null, + null, null); + + CloudProcess result = V3ProcessMapper.toCloudProcess(process); + + Assertions.assertEquals(HealthCheckType.PORT, result.getHealthCheckType()); + Assertions.assertNull(result.getReadinessHealthCheckType()); + } + + @Test + void testToCloudProcessHealthCheckTypeIsUpperCased() { + V3Process process = new V3Process(GUID_STRING, "s", 1, 256, 1024, new V3HealthCheck("process", null), null, null, null, null, + null, null, null, null, null, null); + + Assertions.assertEquals(HealthCheckType.PROCESS, V3ProcessMapper.toCloudProcess(process) + .getHealthCheckType()); + } + + @Test + void testToCloudProcessNullHealthCheckDataLeavesEndpointFieldsNull() { + V3Process process = new V3Process(GUID_STRING, "s", 1, 256, 1024, new V3HealthCheck("port", null), null, null, null, null, null, + null, null, null, null, null); + + CloudProcess result = V3ProcessMapper.toCloudProcess(process); + + Assertions.assertEquals(HealthCheckType.PORT, result.getHealthCheckType()); + Assertions.assertNull(result.getHealthCheckHttpEndpoint()); + Assertions.assertNull(result.getHealthCheckTimeout()); + } + + @Test + void testToCloudProcessWhenCommandNullAndHealthCheckTypeNullDoesNotThrow() { + V3Process process = new V3Process(GUID_STRING, null, 1, 256, 1024, new V3HealthCheck(null, null), null, null, null, null, null, + null, null, null, null, null); + + CloudProcess result = Assertions.assertDoesNotThrow(() -> V3ProcessMapper.toCloudProcess(process)); + + Assertions.assertNull(result.getCommand()); + Assertions.assertEquals(HealthCheckType.PORT, result.getHealthCheckType()); + } + + @Test + void testToCloudProcessWhenCommandNullAndHealthCheckNullDoesNotThrow() { + V3Process process = new V3Process(GUID_STRING, null, 1, 256, 1024, null, null, null, null, null, null, null, null, null, null, + null); + + CloudProcess result = Assertions.assertDoesNotThrow(() -> V3ProcessMapper.toCloudProcess(process)); + + Assertions.assertNull(result.getCommand()); + Assertions.assertEquals(HealthCheckType.PORT, result.getHealthCheckType()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappersTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappersTest.java new file mode 100644 index 0000000000..36c206a403 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappersTest.java @@ -0,0 +1,94 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ResourceMappersTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final UUID GUID = UUID.fromString(GUID_STRING); + private static final String CREATED_AT_STRING = "2026-08-04T10:15:30Z"; + private static final String UPDATED_AT_STRING = "2026-08-05T11:16:31Z"; + + @Test + void testParseMetadataPopulatesAllFields() { + CloudMetadata metadata = V3ResourceMappers.parseMetadata(GUID_STRING, CREATED_AT_STRING, UPDATED_AT_STRING); + + Assertions.assertEquals(GUID, metadata.getGuid()); + Assertions.assertEquals(LocalDateTime.of(2026, 8, 4, 10, 15, 30), metadata.getCreatedAt()); + Assertions.assertEquals(LocalDateTime.of(2026, 8, 5, 11, 16, 31), metadata.getUpdatedAt()); + } + + @Test + void testParseMetadataWithAllNullsReturnsEmptyMetadata() { + CloudMetadata metadata = V3ResourceMappers.parseMetadata(null, null, null); + + Assertions.assertNull(metadata.getGuid()); + Assertions.assertNull(metadata.getCreatedAt()); + Assertions.assertNull(metadata.getUpdatedAt()); + } + + @Test + void testParseNullableGuidWithValidGuid() { + Assertions.assertEquals(GUID, V3ResourceMappers.parseNullableGuid(GUID_STRING)); + } + + @Test + void testParseNullableGuidWithNullReturnsNull() { + Assertions.assertNull(V3ResourceMappers.parseNullableGuid(null)); + } + + @Test + void testParseNullableGuidWithMalformedStringReturnsNull() { + Assertions.assertNull(V3ResourceMappers.parseNullableGuid("not-a-guid")); + } + + @Test + void testParseNullableDateWithValidOffsetDateTime() { + Assertions.assertEquals(LocalDateTime.of(2026, 8, 4, 10, 15, 30), V3ResourceMappers.parseNullableDate(CREATED_AT_STRING)); + } + + @Test + void testParseNullableDateWithNullReturnsNull() { + Assertions.assertNull(V3ResourceMappers.parseNullableDate(null)); + } + + @Test + void testParseNullableDateWithMalformedStringReturnsNull() { + Assertions.assertNull(V3ResourceMappers.parseNullableDate("2026/08/04 10:15")); + } + + @Test + void testToV3MetadataWithNullReturnsNull() { + Assertions.assertNull(V3ResourceMappers.toV3Metadata(null)); + } + + @Test + void testToV3MetadataCopiesLabelsAndAnnotations() { + V3Metadata wire = new V3Metadata(Map.of("label-key", "label-value"), Map.of("annotation-key", "annotation-value")); + + Metadata metadata = V3ResourceMappers.toV3Metadata(wire); + + Assertions.assertEquals(Map.of("label-key", "label-value"), metadata.getLabels()); + Assertions.assertEquals(Map.of("annotation-key", "annotation-value"), metadata.getAnnotations()); + } + + @Test + void testToV3MetadataWithNullMapsReturnsEmptyMaps() { + V3Metadata wire = new V3Metadata(null, null); + + Metadata metadata = V3ResourceMappers.toV3Metadata(wire); + + Assertions.assertTrue(metadata.getLabels() + .isEmpty()); + Assertions.assertTrue(metadata.getAnnotations() + .isEmpty()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java new file mode 100644 index 0000000000..d658ebce3b --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java @@ -0,0 +1,28 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class V3RoleMapperTest { + + @ParameterizedTest + @CsvSource({ "organization_auditor,ORGANIZATION_AUDITOR", "organization_billing_manager,ORGANIZATION_BILLING_MANAGER", + "organization_manager,ORGANIZATION_MANAGER", "organization_user,ORGANIZATION_USER", "space_auditor,SPACE_AUDITOR", + "space_developer,SPACE_DEVELOPER", "space_manager,SPACE_MANAGER" }) + void testToUserRoleMapsEachType(String wireType, UserRole expected) { + V3Role role = new V3Role("guid", null, null, wireType, null); + + Assertions.assertEquals(expected, V3RoleMapper.toUserRole(role)); + } + + @Test + void testToUserRoleWithUnknownTypeThrows() { + V3Role role = new V3Role("guid", null, null, "galactic_overlord", null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> V3RoleMapper.toUserRole(role)); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java new file mode 100644 index 0000000000..98b6fd68b5 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java @@ -0,0 +1,137 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3Destination; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3DestinationApp; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3RouteRelationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3RouteMapperTest { + + private static final String ROUTE_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String DOMAIN_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String APP_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String DESTINATION_GUID = "99999999-8888-7777-6666-555555555555"; + + @Test + void testToCloudRouteComputesHostBasedDomain() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", List.of()); + + CloudRoute result = V3RouteMapper.toCloudRoute(route); + + Assertions.assertEquals("example.com", result.getDomain() + .getName()); + Assertions.assertEquals("myhost", result.getHost()); + } + + @Test + void testToCloudRouteComputesDomainWhenNoHost() { + V3Route route = buildRoute("", "", null, "example.com", List.of()); + + Assertions.assertEquals("example.com", V3RouteMapper.toCloudRoute(route) + .getDomain() + .getName()); + } + + @Test + void testToCloudRouteStripsPathFromDomain() { + V3Route route = buildRoute("myhost", "/foo", null, "myhost.example.com/foo", List.of()); + + Assertions.assertEquals("example.com", V3RouteMapper.toCloudRoute(route) + .getDomain() + .getName()); + } + + @Test + void testToCloudRouteStripsPortFromDomain() { + V3Route route = buildRoute("", "", 8080, "tcp.example.com:8080", List.of()); + + Assertions.assertEquals("tcp.example.com", V3RouteMapper.toCloudRoute(route) + .getDomain() + .getName()); + } + + @Test + void testToCloudRouteCountsAppsUsingRoute() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", + List.of(buildDestination("http1"), buildDestination("http2"))); + + Assertions.assertEquals(2, V3RouteMapper.toCloudRoute(route) + .getAppsUsingRoute()); + } + + @Test + void testToCloudRouteRequestedProtocolIsNullWhenNoApplicationGuidGiven() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", List.of(buildDestination("http2"))); + + Assertions.assertNull(V3RouteMapper.toCloudRoute(route) + .getRequestedProtocol()); + } + + @Test + void testToCloudRouteRequestedProtocolMatchesApplicationGuid() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", List.of(buildDestination("http2"))); + + CloudRoute result = V3RouteMapper.toCloudRoute(route, UUID.fromString(APP_GUID)); + + Assertions.assertEquals("http2", result.getRequestedProtocol()); + } + + @Test + void testToCloudRouteRequestedProtocolIsNullWhenApplicationGuidDoesNotMatch() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", List.of(buildDestination("http2"))); + + CloudRoute result = V3RouteMapper.toCloudRoute(route, UUID.randomUUID()); + + Assertions.assertNull(result.getRequestedProtocol()); + } + + @Test + void testToCloudRouteWhenRelationshipsNullDoesNotThrow() { + V3Route route = new V3Route(ROUTE_GUID, null, null, null, "example.com", null, null, List.of(), null, null); + + CloudRoute result = Assertions.assertDoesNotThrow(() -> V3RouteMapper.toCloudRoute(route)); + + Assertions.assertEquals("example.com", result.getDomain() + .getName()); + Assertions.assertNull(result.getDomain() + .getMetadata() + .getGuid()); + } + + @Test + void testToCloudRouteWhenDestinationAppOrProtocolNullDoesNotThrow() { + V3Destination destinationWithNullApp = new V3Destination(DESTINATION_GUID, null, 8080, 1, "http1"); + V3Destination destinationWithNullProtocol = new V3Destination(DESTINATION_GUID, new V3DestinationApp(APP_GUID), 8080, 1, null); + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", + List.of(destinationWithNullApp, destinationWithNullProtocol)); + + CloudRoute result = Assertions.assertDoesNotThrow(() -> V3RouteMapper.toCloudRoute(route)); + + Assertions.assertEquals(1, result.getDestinations() + .size()); + Assertions.assertEquals(UUID.fromString(APP_GUID), result.getDestinations() + .get(0) + .getApplicationGuid()); + Assertions.assertNull(result.getDestinations() + .get(0) + .getProtocol()); + } + + private static V3Route buildRoute(String host, String path, Integer port, String url, List destinations) { + V3RouteRelationships relationships = new V3RouteRelationships(new V3ToOneRelationship(new V3RelationshipData(DOMAIN_GUID)), + new V3ToOneRelationship(new V3RelationshipData(DOMAIN_GUID))); + return new V3Route(ROUTE_GUID, host, path, port, url, null, null, destinations, null, relationships); + } + + private static V3Destination buildDestination(String protocol) { + return new V3Destination(DESTINATION_GUID, new V3DestinationApp(APP_GUID), 8080, 1, protocol); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapperTest.java new file mode 100644 index 0000000000..a70a5bfa7e --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapperTest.java @@ -0,0 +1,70 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3LastOperation; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3Relationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceBindingMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String APP_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String INSTANCE_GUID = "99999999-8888-7777-6666-555555555555"; + + @Test + void testToCloudServiceBindingMapsAllFields() { + V3LastOperation lastOperation = new V3LastOperation("create", "succeeded", "done", "2026-08-04T10:15:30Z", + "2026-08-04T10:16:30Z"); + V3ServiceBinding binding = new V3ServiceBinding(GUID_STRING, "my-binding", "app", null, null, lastOperation, null, + buildRelationships(APP_GUID, INSTANCE_GUID)); + + CloudServiceBinding result = V3ServiceBindingMapper.toCloudServiceBinding(binding); + + Assertions.assertEquals(APP_GUID, result.getApplicationGuid() + .toString()); + Assertions.assertEquals(INSTANCE_GUID, result.getServiceInstanceGuid() + .toString()); + Assertions.assertEquals(ServiceCredentialBindingOperation.Type.CREATE, result.getServiceBindingOperation() + .getType()); + Assertions.assertEquals(ServiceCredentialBindingOperation.State.SUCCEEDED, result.getServiceBindingOperation() + .getState()); + } + + @Test + void testToCloudServiceBindingNullApplicationRelationshipReturnsNullApplicationGuid() { + V3Relationships relationships = new V3Relationships(null, new V3ToOneRelationship(new V3RelationshipData(INSTANCE_GUID))); + V3ServiceBinding binding = new V3ServiceBinding(GUID_STRING, "app-binding", "app", null, null, null, null, relationships); + + Assertions.assertNull(V3ServiceBindingMapper.toCloudServiceBinding(binding) + .getApplicationGuid()); + } + + @Test + void testToCloudServiceBindingNullApplicationDataReturnsNullApplicationGuid() { + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(null), + new V3ToOneRelationship(new V3RelationshipData(INSTANCE_GUID))); + V3ServiceBinding binding = new V3ServiceBinding(GUID_STRING, "b", "app", null, null, null, null, relationships); + + Assertions.assertNull(V3ServiceBindingMapper.toCloudServiceBinding(binding) + .getApplicationGuid()); + } + + @Test + void testToCloudServiceBindingNullLastOperationReturnsNullOperation() { + V3ServiceBinding binding = new V3ServiceBinding(GUID_STRING, "b", "app", null, null, null, null, + buildRelationships(APP_GUID, INSTANCE_GUID)); + + Assertions.assertNull(V3ServiceBindingMapper.toCloudServiceBinding(binding) + .getServiceBindingOperation()); + } + + private static V3Relationships buildRelationships(String appGuid, String instanceGuid) { + return new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(appGuid)), + new V3ToOneRelationship(new V3RelationshipData(instanceGuid))); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapperTest.java new file mode 100644 index 0000000000..255a5c68c8 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapperTest.java @@ -0,0 +1,44 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker.V3Relationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceBrokerMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String SPACE_GUID = "11111111-2222-3333-4444-555555555555"; + + @Test + void testToCloudServiceBrokerMapsAllFields() { + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(SPACE_GUID))); + V3ServiceBroker broker = new V3ServiceBroker(GUID_STRING, "my-broker", "https://broker", null, null, null, relationships); + + CloudServiceBroker result = V3ServiceBrokerMapper.toCloudServiceBroker(broker); + + Assertions.assertEquals("my-broker", result.getName()); + Assertions.assertEquals("https://broker", result.getUrl()); + Assertions.assertEquals(SPACE_GUID, result.getSpaceGuid()); + } + + @Test + void testToCloudServiceBrokerNullRelationshipsReturnNullSpaceGuid() { + V3ServiceBroker broker = new V3ServiceBroker(GUID_STRING, "my-broker", "https://broker", null, null, null, null); + + Assertions.assertNull(V3ServiceBrokerMapper.toCloudServiceBroker(broker) + .getSpaceGuid()); + } + + @Test + void testToCloudServiceBrokerNullSpaceDataReturnsNullSpaceGuid() { + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(null)); + V3ServiceBroker broker = new V3ServiceBroker(GUID_STRING, "my-broker", "https://broker", null, null, null, relationships); + + Assertions.assertNull(V3ServiceBrokerMapper.toCloudServiceBroker(broker) + .getSpaceGuid()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java new file mode 100644 index 0000000000..828d3fe6ea --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java @@ -0,0 +1,104 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance.V3LastOperation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceInstanceMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudServiceInstanceMapsAllFields() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "my-service", "managed", null, null, List.of("tag1", "tag2"), + "syslog://drain", null, new V3LastOperation("create", "succeeded", "done"), null, + null); + + CloudServiceInstance result = V3ServiceInstanceMapper.toCloudServiceInstance(instance, "my-plan", "my-label"); + + Assertions.assertEquals("my-service", result.getName()); + Assertions.assertEquals("my-plan", result.getPlan()); + Assertions.assertEquals("my-label", result.getLabel()); + Assertions.assertEquals(ServiceInstanceType.MANAGED, result.getType()); + Assertions.assertEquals(List.of("tag1", "tag2"), result.getTags()); + Assertions.assertEquals("syslog://drain", result.getSyslogDrainUrl()); + Assertions.assertEquals(ServiceOperation.Type.CREATE, result.getLastOperation() + .getType()); + Assertions.assertEquals(ServiceOperation.State.SUCCEEDED, result.getLastOperation() + .getState()); + } + + @Test + void testToCloudServiceInstanceWithUserProvidedType() { + V3ServiceInstance instance = buildInstanceWithType("user-provided"); + + Assertions.assertEquals(ServiceInstanceType.USER_PROVIDED, V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getType()); + } + + @Test + void testToCloudServiceInstanceWithNullTypeReturnsNullType() { + V3ServiceInstance instance = buildInstanceWithType(null); + + Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getType()); + } + + @Test + void testToCloudServiceInstanceWithUnknownTypeReturnsNullType() { + V3ServiceInstance instance = buildInstanceWithType("bogus"); + + Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getType()); + } + + @Test + void testToCloudServiceInstanceNullTagsReturnEmptyList() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, null, null, null); + + Assertions.assertTrue(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getTags() + .isEmpty()); + } + + @Test + void testToCloudServiceInstanceNullLastOperationReturnsNullOperation() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, null, null, null); + + Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getLastOperation()); + } + + @Test + void testToCloudServiceInstanceLastOperationWithNullTypeReturnsNullOperation() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, + new V3LastOperation(null, "succeeded", "d"), null, null); + + Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getLastOperation()); + } + + @Test + void testToCloudServiceInstanceWithoutAuxiliaryContentExcludesPlanAndOperation() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "my-service", "managed", null, null, List.of("t"), "syslog", null, + new V3LastOperation("create", "succeeded", "d"), null, null); + + CloudServiceInstance result = V3ServiceInstanceMapper.toCloudServiceInstanceWithoutAuxiliaryContent(instance); + + Assertions.assertEquals("my-service", result.getName()); + Assertions.assertEquals(List.of("t"), result.getTags()); + Assertions.assertNull(result.getPlan()); + Assertions.assertNull(result.getLastOperation()); + Assertions.assertNull(result.getType()); + } + + private static V3ServiceInstance buildInstanceWithType(String type) { + return new V3ServiceInstance(GUID_STRING, "s", type, null, null, null, null, null, null, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapperTest.java new file mode 100644 index 0000000000..8818c2d95e --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapperTest.java @@ -0,0 +1,50 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3LastOperation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceKeyMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudServiceKeyMapsNameAndCredentials() { + V3ServiceBinding key = new V3ServiceBinding(GUID_STRING, "my-key", "key", null, null, null, null, null); + + CloudServiceKey result = V3ServiceKeyMapper.toCloudServiceKey(key, null, Map.of("username", "u", "password", "p")); + + Assertions.assertEquals("my-key", result.getName()); + Assertions.assertEquals(Map.of("username", "u", "password", "p"), result.getCredentials()); + Assertions.assertNull(result.getServiceKeyOperation()); + } + + @Test + void testToCloudServiceKeyWithoutCredentialsReturnsNullCredentials() { + V3ServiceBinding key = new V3ServiceBinding(GUID_STRING, "my-key", "key", null, null, null, null, null); + + Assertions.assertNull(V3ServiceKeyMapper.toCloudServiceKey(key, null) + .getCredentials()); + } + + @Test + void testToCloudServiceKeyMapsLastOperation() { + V3LastOperation lastOperation = new V3LastOperation("create", "succeeded", "done", "2026-08-04T10:15:30Z", + "2026-08-04T10:16:30Z"); + V3ServiceBinding key = new V3ServiceBinding(GUID_STRING, "my-key", "key", null, null, lastOperation, null, null); + + CloudServiceKey result = V3ServiceKeyMapper.toCloudServiceKey(key, null, null); + + Assertions.assertEquals(ServiceCredentialBindingOperation.Type.CREATE, result.getServiceKeyOperation() + .getType()); + Assertions.assertEquals(ServiceCredentialBindingOperation.State.SUCCEEDED, result.getServiceKeyOperation() + .getState()); + Assertions.assertEquals("done", result.getServiceKeyOperation() + .getDescription()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java new file mode 100644 index 0000000000..77a49e5a05 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java @@ -0,0 +1,73 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3BrokerCatalog; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3Features; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3Relationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceOfferingMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String BROKER_GUID = "11111111-2222-3333-4444-555555555555"; + + @Test + void testToCloudServiceOfferingMapsAllFields() { + V3Features features = new V3Features(true, null, null, null, null); + V3BrokerCatalog brokerCatalog = new V3BrokerCatalog("catalog-id", Map.of("displayName", "My Service"), features); + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(BROKER_GUID))); + V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "my-offering", "An offering", true, true, "https://docs", + null, null, brokerCatalog, relationships, null, null); + + CloudServiceOffering result = V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()); + + Assertions.assertEquals("my-offering", result.getName()); + Assertions.assertEquals("An offering", result.getDescription()); + Assertions.assertTrue(result.isAvailable()); + Assertions.assertTrue(result.isBindable()); + Assertions.assertTrue(result.isShareable()); + Assertions.assertEquals("https://docs", result.getDocUrl()); + Assertions.assertEquals("catalog-id", result.getUniqueId()); + Assertions.assertEquals(BROKER_GUID, result.getBrokerId()); + Assertions.assertEquals(Map.of("displayName", "My Service"), result.getExtra()); + } + + @Test + void testToCloudServiceOfferingNullBrokerCatalogReturnsNullDerivedFields() { + V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, null, null, null, null); + + CloudServiceOffering result = V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()); + + Assertions.assertNull(result.isBindable()); + Assertions.assertNull(result.getUniqueId()); + Assertions.assertNull(result.getExtra()); + Assertions.assertNull(result.getBrokerId()); + } + + @Test + void testToCloudServiceOfferingNullFeaturesReturnsNullBindable() { + V3BrokerCatalog brokerCatalog = new V3BrokerCatalog("catalog-id", null, null); + V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, brokerCatalog, null, + null, null); + + Assertions.assertNull(V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()) + .isBindable()); + } + + @Test + void testToCloudServiceOfferingNullServiceBrokerRelationshipReturnsNullBrokerId() { + V3Relationships relationships = new V3Relationships(null); + V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, null, relationships, + null, null); + + Assertions.assertNull(V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()) + .getBrokerId()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java new file mode 100644 index 0000000000..f075591063 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java @@ -0,0 +1,71 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan.V3BrokerCatalog; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan.V3Relationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServicePlanMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String OFFERING_GUID = "11111111-2222-3333-4444-555555555555"; + public static final String SERVICE_BROKER_SPACE_GUID = "11111111-2222-3333-4444-555555555555"; + + @Test + void testToCloudServicePlanMapsAllFields() { + V3BrokerCatalog brokerCatalog = new V3BrokerCatalog("catalog-id", Map.of("k", "v")); + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(OFFERING_GUID)), + new V3ToOneRelationship(new V3RelationshipData(SERVICE_BROKER_SPACE_GUID))); + V3ServicePlan plan = new V3ServicePlan(GUID_STRING, "my-plan", "A plan", true, "public", null, null, null, null, null, null, + brokerCatalog, + relationships, null, null); + + CloudServicePlan result = V3ServicePlanMapper.toCloudServicePlan(plan); + + Assertions.assertEquals("my-plan", result.getName()); + Assertions.assertEquals("A plan", result.getDescription()); + Assertions.assertTrue(result.isFree()); + Assertions.assertTrue(result.isPublic()); + Assertions.assertEquals("catalog-id", result.getUniqueId()); + Assertions.assertEquals(OFFERING_GUID, result.getServiceOfferingId()); + Assertions.assertEquals(Map.of("k", "v"), result.getExtra()); + } + + @Test + void testToCloudServicePlanNonPublicVisibilityIsNotPublic() { + V3ServicePlan plan = buildPlanWithVisibility("organization"); + + Assertions.assertFalse(V3ServicePlanMapper.toCloudServicePlan(plan) + .isPublic()); + } + + @Test + void testToCloudServicePlanNullVisibilityReturnsNullIsPublic() { + V3ServicePlan plan = buildPlanWithVisibility(null); + + Assertions.assertNull(V3ServicePlanMapper.toCloudServicePlan(plan) + .isPublic()); + } + + @Test + void testToCloudServicePlanNullBrokerCatalogAndRelationshipsReturnNulls() { + V3ServicePlan plan = new V3ServicePlan(GUID_STRING, "p", null, false, "public", null, null, null, null, null, null, null, null, + null, null); + + CloudServicePlan result = V3ServicePlanMapper.toCloudServicePlan(plan); + + Assertions.assertNull(result.getUniqueId()); + Assertions.assertNull(result.getExtra()); + Assertions.assertNull(result.getServiceOfferingId()); + } + + private static V3ServicePlan buildPlanWithVisibility(String visibilityType) { + return new V3ServicePlan(GUID_STRING, "p", null, false, visibilityType, null, null, null, null, null, null, null, null, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java new file mode 100644 index 0000000000..39ef332ad3 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java @@ -0,0 +1,33 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3StackMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudStackMapsAllFields() { + V3Stack stack = new V3Stack(GUID_STRING, "cflinuxfs4", "Ubuntu-based stack", "2026-08-04T10:15:30Z", "2026-08-04T10:16:30Z", null, + null); + + CloudStack result = V3StackMapper.toCloudStack(stack); + + Assertions.assertEquals("cflinuxfs4", result.getName()); + Assertions.assertEquals("Ubuntu-based stack", result.getDescription()); + Assertions.assertEquals(GUID_STRING, result.getGuid() + .toString()); + } + + @Test + void testToCloudStackWithNullGuidReturnsNullGuid() { + V3Stack stack = new V3Stack(null, "cflinuxfs4", null, null, null, null, null); + + Assertions.assertNull(V3StackMapper.toCloudStack(stack) + .getMetadata() + .getGuid()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java new file mode 100644 index 0000000000..2b568e9b83 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java @@ -0,0 +1,65 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3TaskMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudTaskMapsAllFields() { + V3Task task = new V3Task(GUID_STRING, "migrate-db", "rails db:migrate", "SUCCEEDED", 256, 1024, + new V3Task.V3Result(null), "2026-08-04T10:15:30Z", "2026-08-04T10:16:30Z", null, null); + + CloudTask result = V3TaskMapper.toCloudTask(task); + + Assertions.assertEquals("migrate-db", result.getName()); + Assertions.assertEquals("rails db:migrate", result.getCommand()); + Assertions.assertEquals(CloudTask.State.SUCCEEDED, result.getState()); + Assertions.assertEquals(256, result.getLimits() + .getMemory()); + Assertions.assertEquals(1024, result.getLimits() + .getDisk()); + Assertions.assertNull(result.getResult() + .getFailureReason()); + } + + @Test + void testToCloudTaskWithNullResultReturnsNullResult() { + V3Task task = buildTaskWithState("RUNNING"); + + Assertions.assertNull(V3TaskMapper.toCloudTask(task) + .getResult()); + } + + @Test + void testToCloudTaskMapsFailureReason() { + V3Task task = new V3Task(GUID_STRING, "t", "cmd", "FAILED", 256, 1024, new V3Task.V3Result("boom"), null, null, null, null); + + Assertions.assertEquals("boom", V3TaskMapper.toCloudTask(task) + .getResult() + .getFailureReason()); + } + + @Test + void testToCloudTaskWithNullStateReturnsNullState() { + V3Task task = buildTaskWithState(null); + + Assertions.assertNull(V3TaskMapper.toCloudTask(task) + .getState()); + } + + @Test + void testToCloudTaskWithUnknownStateThrows() { + V3Task task = buildTaskWithState("bogus"); + + Assertions.assertThrows(IllegalArgumentException.class, () -> V3TaskMapper.toCloudTask(task)); + } + + private static V3Task buildTaskWithState(String state) { + return new V3Task(GUID_STRING, "t", "cmd", state, 256, 1024, null, null, null, null, null); + } + +} diff --git a/multiapps-controller-core/src/main/java/module-info.java b/multiapps-controller-core/src/main/java/module-info.java index b00243ee13..d24aaceea8 100644 --- a/multiapps-controller-core/src/main/java/module-info.java +++ b/multiapps-controller-core/src/main/java/module-info.java @@ -45,8 +45,6 @@ requires transitive org.cloudfoundry.multiapps.controller.client; requires transitive org.cloudfoundry.multiapps.controller.persistence; requires transitive org.cloudfoundry.multiapps.mta; - - requires org.cloudfoundry.client; requires com.fasterxml.jackson.annotation; requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/CloudControllerClientFactory.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/CloudControllerClientFactory.java index 91fd2fe929..07a0a572b6 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/CloudControllerClientFactory.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/CloudControllerClientFactory.java @@ -68,8 +68,7 @@ public CloudControllerClient createClient(OAuth2AccessTokenWithAdditionalInfo to CloudCredentials credentials = createCredentials(token); oAuthClient.init(credentials); - var spaceClient = clientFactory.getCloudFoundryClientFactory() - .createSpaceClient(configuration.getControllerUrl(), oAuthClient, requestTags); + var spaceClient = clientFactory.createSpaceClient(configuration.getControllerUrl(), oAuthClient, requestTags); CloudSpace target = spaceClient.getSpace(UUID.fromString(spaceId)); CloudControllerRestClient controllerClient = clientFactory.createClient(configuration.getControllerUrl(), credentials, target, @@ -82,8 +81,7 @@ public CloudSpaceClient createSpaceClient(OAuth2AccessTokenWithAdditionalInfo to CloudCredentials credentials = createCredentials(token); oAuthClient.init(credentials); var requestTags = buildRequestTags(StringUtils.EMPTY); - return clientFactory.getCloudFoundryClientFactory() - .createSpaceClient(configuration.getControllerUrl(), oAuthClient, requestTags); + return clientFactory.createSpaceClient(configuration.getControllerUrl(), oAuthClient, requestTags); } public LogCacheClient createLogCacheClient(OAuth2AccessTokenWithAdditionalInfo token, String correlationId) { @@ -91,8 +89,7 @@ public LogCacheClient createLogCacheClient(OAuth2AccessTokenWithAdditionalInfo t CloudCredentials credentials = createCredentials(token); oAuthClient.init(credentials); var requestTags = buildRequestTags(correlationId); - return clientFactory.getCloudFoundryClientFactory() - .createLogCacheClient(configuration.getControllerUrl(), oAuthClient, requestTags); + return clientFactory.createLogCacheClient(configuration.getControllerUrl(), oAuthClient, requestTags); } private CloudCredentials createCredentials(OAuth2AccessTokenWithAdditionalInfo token) { diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java index 8d01fe7eca..2e73410bfe 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java @@ -11,12 +11,13 @@ import java.util.Optional; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; import org.cloudfoundry.multiapps.controller.core.model.ImmutableDeployedMtaServiceKey; @@ -78,7 +79,7 @@ public static CloudMetadata getCloudMetadataV3(Map resource) { @SuppressWarnings("unchecked") public static Metadata getMetadataV3(Map resource) { Map metadata = (Map) resource.getOrDefault("metadata", Collections.emptyMap()); - return Metadata.builder() + return ImmutableMetadata.builder() .labels((Map) metadata.getOrDefault("labels", Collections.emptyMap())) .annotations((Map) metadata.getOrDefault("annotations", Collections.emptyMap())) .build(); diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClient.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClient.java index 977ddae39b..d9e6a303f3 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClient.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClient.java @@ -7,7 +7,7 @@ import java.util.UUID; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java index f9503d54ee..290ed3b8ae 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java @@ -7,7 +7,7 @@ import jakarta.inject.Inject; import jakarta.inject.Named; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java index 59b674ab9e..2f1606a70f 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java @@ -4,8 +4,9 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.springframework.util.DigestUtils; @@ -42,15 +43,15 @@ public static String getHashedLabel(String mtaLabel) { } public static Metadata getMetadataWithoutMtaFields(Metadata metadata) { - return Metadata.builder() + return ImmutableMetadata.builder() .from(metadata) - .label(MtaMetadataLabels.MTA_ID, null) - .label(MtaMetadataLabels.MTA_NAMESPACE, null) - .label(MtaMetadataLabels.SPACE_GUID, null) - .annotation(MtaMetadataAnnotations.MTA_ID, null) - .annotation(MtaMetadataAnnotations.MTA_VERSION, null) - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, null) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, null) + .putLabel(MtaMetadataLabels.MTA_ID, null) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, null) + .putLabel(MtaMetadataLabels.SPACE_GUID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, null) + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, null) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, null) .build(); } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java index 44f9dee90b..345f7b2725 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java @@ -5,7 +5,8 @@ import java.util.TreeMap; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.common.util.MapUtil; import org.cloudfoundry.multiapps.controller.core.Constants; @@ -21,11 +22,11 @@ public static Metadata build(DeploymentDescriptor deploymentDescriptor, String n String mtaModuleProvidedDependenciesAnnotation = buildMtaModuleProvidedDependenciesAnnotation(module); String mtaServicesAnnotation = buildBoundMtaServicesAnnotation(services); - Metadata.Builder builder = MtaMetadataBuilder.init(deploymentDescriptor, namespace) - .annotation(MtaMetadataAnnotations.MTA_MODULE, mtaModuleAnnotation) - .annotation(MtaMetadataAnnotations.MTA_MODULE_PUBLIC_PROVIDED_DEPENDENCIES, + ImmutableMetadata.Builder builder = MtaMetadataBuilder.init(deploymentDescriptor, namespace) + .putAnnotation(MtaMetadataAnnotations.MTA_MODULE, mtaModuleAnnotation) + .putAnnotation(MtaMetadataAnnotations.MTA_MODULE_PUBLIC_PROVIDED_DEPENDENCIES, mtaModuleProvidedDependenciesAnnotation) - .annotation(MtaMetadataAnnotations.MTA_MODULE_BOUND_SERVICES, mtaServicesAnnotation); + .putAnnotation(MtaMetadataAnnotations.MTA_MODULE_BOUND_SERVICES, mtaServicesAnnotation); return builder.build(); } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java index 9b9a0d18db..3be51b4c5c 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java @@ -1,8 +1,8 @@ package org.cloudfoundry.multiapps.controller.core.cf.v2; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Metadata.Builder; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata.Builder; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.core.cf.metadata.util.MtaMetadataUtil; @@ -12,15 +12,15 @@ public class MtaMetadataBuilder { public static Builder init(DeploymentDescriptor deploymentDescriptor, String namespace) { String hashedMtaId = MtaMetadataUtil.getHashedLabel(deploymentDescriptor.getId()); - Builder builder = Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, hashedMtaId) - .annotation(MtaMetadataAnnotations.MTA_ID, deploymentDescriptor.getId()) - .annotation(MtaMetadataAnnotations.MTA_VERSION, deploymentDescriptor.getVersion()); + Builder builder = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, hashedMtaId) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, deploymentDescriptor.getId()) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, deploymentDescriptor.getVersion()); if (StringUtils.isNotEmpty(namespace)) { String hashedMtaNamespace = MtaMetadataUtil.getHashedLabel(namespace); - builder.label(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, namespace); + builder.putLabel(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, namespace); } return builder; } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java index ae13c007af..78817ec81c 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java @@ -1,7 +1,7 @@ package org.cloudfoundry.multiapps.controller.core.cf.v2; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Metadata.Builder; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata.Builder; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; @@ -10,7 +10,7 @@ public class ServiceKeyMetadataBuilder { public static Metadata build(DeploymentDescriptor deploymentDescriptor, String namespace, String spaceGuid) { Builder builder = MtaMetadataBuilder.init(deploymentDescriptor, namespace) - .label(MtaMetadataLabels.SPACE_GUID, spaceGuid); + .putLabel(MtaMetadataLabels.SPACE_GUID, spaceGuid); return builder.build(); } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeysCloudModelBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeysCloudModelBuilder.java index ef5cb4a984..1ad059b028 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeysCloudModelBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeysCloudModelBuilder.java @@ -6,8 +6,8 @@ import java.util.Map; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.common.ContentException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java index 2bbc6befb9..b995e3fe74 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java @@ -3,8 +3,8 @@ import java.util.Map; import java.util.TreeMap; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Metadata.Builder; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata.Builder; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.common.util.MapUtil; import org.cloudfoundry.multiapps.controller.core.Constants; @@ -18,7 +18,7 @@ public static Metadata build(DeploymentDescriptor deploymentDescriptor, String n String mtaResourceAnnotation = buildMtaResourceAnnotation(resource); Builder builder = MtaMetadataBuilder.init(deploymentDescriptor, namespace) - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, mtaResourceAnnotation); + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, mtaResourceAnnotation); return builder.build(); } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java index 90abef06be..70e33a2be6 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java @@ -7,7 +7,7 @@ import java.util.Objects; import java.util.TreeMap; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.common.ContentException; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClientTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClientTest.java index 670dcfaec3..ffddd0f832 100644 --- a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClientTest.java +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClientTest.java @@ -6,8 +6,8 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java index e61ff34873..69454f4973 100644 --- a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java @@ -9,7 +9,6 @@ import java.util.stream.Stream; import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.test.Tester; import org.cloudfoundry.multiapps.common.test.Tester.Expectation; @@ -20,6 +19,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.entity.processor.MtaMetadataApplicationCollector; import org.cloudfoundry.multiapps.controller.core.cf.metadata.entity.processor.MtaMetadataEntityAggregator; import org.cloudfoundry.multiapps.controller.core.cf.metadata.entity.processor.MtaMetadataEntityCollector; @@ -259,7 +259,7 @@ private CloudApplication toCloudApplication() { .guid(NameUtil.getUUID(name)) .build()) .name(name) - .v3Metadata(Metadata.builder() + .v3Metadata(ImmutableMetadata.builder() .annotations(annotations) .labels(labels) .build()) @@ -279,7 +279,7 @@ private CloudServiceInstance toCloudService() { .guid(NameUtil.getUUID(name)) .build()) .name(name) - .v3Metadata(Metadata.builder() + .v3Metadata(ImmutableMetadata.builder() .annotations(annotations) .labels(labels) .build()) diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/ApplicationConfigurationTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/ApplicationConfigurationTest.java index af8e25b17a..51c769f922 100644 --- a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/ApplicationConfigurationTest.java +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/ApplicationConfigurationTest.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.Map; -import io.jsonwebtoken.lang.Strings; +import org.apache.commons.lang3.StringUtils; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.core.Messages; @@ -613,8 +613,8 @@ void testIsOnStartFilesWithoutContentCleanerEnabledThroughEnvironmentWithEnv() { @Test void testLoad() { - when(environment.getString(ApplicationConfiguration.OBJECTSTORE_REGIONS, Strings.EMPTY)) - .thenReturn(Strings.EMPTY); + when(environment.getString(ApplicationConfiguration.OBJECTSTORE_REGIONS, StringUtils.EMPTY)) + .thenReturn(StringUtils.EMPTY); Map vcapApplication = injectFileInEnvironment(VCAP_APPLICATION, ApplicationConfiguration.CFG_VCAP_APPLICATION); configuration.load(); Assertions.assertEquals(vcapApplication.get("cf_api"), configuration.getControllerUrl() diff --git a/multiapps-controller-persistence/pom.xml b/multiapps-controller-persistence/pom.xml index 065c46b790..a45803f9f4 100644 --- a/multiapps-controller-persistence/pom.xml +++ b/multiapps-controller-persistence/pom.xml @@ -19,7 +19,8 @@ maven-surefire-plugin - ${project.basedir}/src/test/resources/logging.properties + ${project.basedir}/src/test/resources/logging.properties + @@ -90,10 +91,6 @@ org.glassfish.jaxb jaxb-core - - org.glassfish.jaxb - jaxb-runtime - com.h2database h2 diff --git a/multiapps-controller-process/pom.xml b/multiapps-controller-process/pom.xml index 86f9727179..664d4c4957 100644 --- a/multiapps-controller-process/pom.xml +++ b/multiapps-controller-process/pom.xml @@ -33,10 +33,6 @@ org.apache.commons commons-compress - - jakarta.xml.bind - jakarta.xml.bind-api - org.glassfish.jaxb jaxb-core diff --git a/multiapps-controller-process/src/main/java/module-info.java b/multiapps-controller-process/src/main/java/module-info.java index 6a67ce6fab..e26da0ff1f 100644 --- a/multiapps-controller-process/src/main/java/module-info.java +++ b/multiapps-controller-process/src/main/java/module-info.java @@ -22,8 +22,6 @@ requires transitive flowable.engine; requires transitive org.cloudfoundry.multiapps.controller.api; requires transitive org.cloudfoundry.multiapps.controller.core; - - requires org.cloudfoundry.client; requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; requires com.google.common; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java index afb3fbdce1..0685aa3a61 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java @@ -8,7 +8,6 @@ import java.util.Set; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.ApplicationServicesUpdateCallback; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.ServiceBindingOperationCallback; @@ -32,6 +31,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; @@ -275,12 +275,6 @@ public List getApplicationRoutes(UUID applicationGuid) { return delegate.getApplicationRoutes(applicationGuid); } - @Override - public boolean getApplicationSshEnabled(UUID applicationGuid) { - logger.debug(Messages.GETTING_SSH_ENABLED_FOR_APPLICATION_0, applicationGuid); - return delegate.getApplicationSshEnabled(applicationGuid); - } - @Override public Map getApplicationFeatures(UUID applicationGuid) { logger.debug(Messages.GETTING_FEATURES_FOR_APPLICATION_0, applicationGuid); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java index ef348f7181..9b8fbabb61 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java @@ -8,8 +8,10 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableCloudFoundryClientFactory; +import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudSpaceClient; +import org.cloudfoundry.multiapps.controller.client.facade.rest.ImmutableCloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.core.auditlogging.MtaConfigurationPurgerAuditLog; import org.cloudfoundry.multiapps.controller.core.cf.OAuthClientFactory; import org.cloudfoundry.multiapps.controller.core.util.ApplicationConfiguration; @@ -97,13 +99,13 @@ protected void initSpaceClient() { CloudCredentials cloudCredentials = new CloudCredentials(configuration.getGlobalAuditorUser(), configuration.getGlobalAuditorPassword(), SecurityUtil.CLIENT_ID, SecurityUtil.CLIENT_SECRET, configuration.getGlobalAuditorOrigin()); - var clientFactory = ImmutableCloudFoundryClientFactory.builder() - .connectTimeout(Duration.ofMinutes(5)) - .responseTimeout(Duration.ofMinutes(5)) - .connectionPoolSize(1) - .threadPoolSize(1) - .build(); - var oauthClient = oAuthClientFactory.createOAuthClient(); + CloudControllerRestClientFactory clientFactory = ImmutableCloudControllerRestClientFactory.builder() + .connectTimeout(Duration.ofMinutes(5)) + .responseTimeout(Duration.ofMinutes(5)) + .connectionPoolSize(1) + .threadPoolSize(1) + .build(); + OAuthClient oauthClient = oAuthClientFactory.createOAuthClient(); oauthClient.init(cloudCredentials); spaceClient = clientFactory.createSpaceClient(configuration.getControllerUrl(), oauthClient, Collections.emptyMap()); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java index dd6569a455..47ea04dc72 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java @@ -8,8 +8,8 @@ import jakarta.inject.Inject; import jakarta.inject.Named; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.processes.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; @@ -85,16 +85,16 @@ private AdditionalModuleParametersReporter buildAdditionalModuleParametersReport private Staging modifyHealthCheckType(Staging staging) { String healthCheckType = staging.getHealthCheckType(); if (StringUtils.isEmpty(healthCheckType)) { - getStepLogger().debug(Messages.NOT_SPECIFIED_HEALTH_CHECK_TYPE, HealthCheckType.PORT.getValue()); + getStepLogger().debug(Messages.NOT_SPECIFIED_HEALTH_CHECK_TYPE, HealthCheckType.PORT.toString()); return ImmutableStaging.copyOf(staging) - .withHealthCheckType(HealthCheckType.PORT.getValue()); + .withHealthCheckType(HealthCheckType.PORT.toString()); } - if (HealthCheckType.NONE.getValue() + if (HealthCheckType.NONE.toString() .equalsIgnoreCase(healthCheckType)) { getStepLogger().info(Messages.USING_DEPRECATED_HEALTH_CHECK_TYPE_0_SETTING_TO_1, healthCheckType, - HealthCheckType.PROCESS.getValue()); + HealthCheckType.PROCESS.toString()); return ImmutableStaging.copyOf(staging) - .withHealthCheckType(HealthCheckType.PROCESS.getValue()); + .withHealthCheckType(HealthCheckType.PROCESS.toString()); } return staging; } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ClearMtaMetadataBaseStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ClearMtaMetadataBaseStep.java index 11a92109fd..b4cf434a2c 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ClearMtaMetadataBaseStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ClearMtaMetadataBaseStep.java @@ -4,7 +4,7 @@ import java.util.UUID; import org.apache.commons.collections4.MapUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java index da5a65dc5e..bcb90f5197 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java @@ -16,13 +16,13 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ObjectUtils; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStep.java index 4a0dfe41ad..d0fe2541e3 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStep.java @@ -8,7 +8,7 @@ import jakarta.inject.Named; import org.apache.commons.collections4.ListUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java index ec99aa294e..c44512a817 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java @@ -3,9 +3,9 @@ import java.text.MessageFormat; import jakarta.inject.Named; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.core.cf.metadata.util.MtaMetadataUtil; @@ -35,12 +35,12 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { getStepLogger().info(Messages.RENAMING_APPLICATION_0_TO_1_TO_BE_USED_FOR_ROLLBACK, cloudApplication.getName(), newApplicationName); client.rename(cloudApplication.getName(), newApplicationName); String hashedMtaNamespace = MtaMetadataUtil.getHashedLabel(mtaUserNamespaceWithSystemNamespace); - client.updateApplicationMetadata(cloudApplication.getGuid(), Metadata.builder() - .from(cloudApplication.getV3Metadata()) - .label(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, - mtaUserNamespaceWithSystemNamespace) - .build()); + client.updateApplicationMetadata(cloudApplication.getGuid(), ImmutableMetadata.builder() + .from(cloudApplication.getV3Metadata()) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, + mtaUserNamespaceWithSystemNamespace) + .build()); return StepPhase.DONE; } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java index 78bff75200..b4441df77d 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java @@ -1,8 +1,8 @@ package org.cloudfoundry.multiapps.controller.process.steps; import jakarta.inject.Named; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.core.cf.metadata.util.MtaMetadataUtil; @@ -28,10 +28,10 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { for (DeployedMtaApplication application : backupMta.getApplications()) { getStepLogger().debug(Messages.REMOVE_MTA_BACKUP_METADATA_FOR_APPLICATION_0, application.getName()); - client.updateApplicationMetadata(application.getGuid(), Metadata.builder() + client.updateApplicationMetadata(application.getGuid(), ImmutableMetadata.builder() .from(application.getV3Metadata()) - .label(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, mtaNamespace) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, mtaNamespace) .build()); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java index 8a1036276a..1efcd4b4ec 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java @@ -14,7 +14,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; @@ -23,6 +23,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.core.cf.CloudHandlerFactory; import org.cloudfoundry.multiapps.controller.core.cf.detect.AppSuffixDeterminer; @@ -294,8 +295,8 @@ public static void setAsJsonBinary(VariableScope scope, String name, Object valu public static void disableAutoscaling(ProcessContext context, CloudControllerClient client, UUID uuid) { String correlationId = context.getVariable(Variables.CORRELATION_ID); - Metadata metadata = Metadata.builder() - .label(MtaMetadataLabels.AUTOSCALER_LABEL, + Metadata metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.AUTOSCALER_LABEL, MessageFormat.format(Messages.DISABLE_AUTOSCALER_LABEL_CONTENT, correlationId)) .build(); client.updateApplicationMetadata(uuid, metadata); @@ -303,8 +304,8 @@ public static void disableAutoscaling(ProcessContext context, CloudControllerCli public static void enableAutoscaling(CloudControllerClient client, CloudApplication application) { UUID applicationId = client.getApplicationGuid(application.getName()); - Metadata metadata = Metadata.builder() - .label(MtaMetadataLabels.AUTOSCALER_LABEL, null) + Metadata metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.AUTOSCALER_LABEL, null) .build(); client.updateApplicationMetadata(applicationId, metadata); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AsyncJobToAsyncExecutionStateAdapter.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AsyncJobToAsyncExecutionStateAdapter.java index bad6812d53..46e6bc95ed 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AsyncJobToAsyncExecutionStateAdapter.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AsyncJobToAsyncExecutionStateAdapter.java @@ -3,8 +3,8 @@ import java.text.MessageFormat; import java.util.function.Consumer; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.steps.AsyncExecutionState; import org.immutables.value.Value; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java index 9fb4a67889..3127360fa3 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java @@ -9,15 +9,16 @@ import jakarta.inject.Inject; import jakarta.inject.Named; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.api.model.ImmutableOperation; import org.cloudfoundry.multiapps.controller.api.model.Operation; import org.cloudfoundry.multiapps.controller.api.model.Operation.State; import org.cloudfoundry.multiapps.controller.api.model.ProcessType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.core.auditlogging.CloudLoggingServiceConfigurationAuditLog; import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; +import org.cloudfoundry.multiapps.controller.core.cloudlogging.CloudLoggingServiceClient; import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaApplication; import org.cloudfoundry.multiapps.controller.core.util.LoggingUtil; @@ -25,13 +26,12 @@ import org.cloudfoundry.multiapps.controller.persistence.model.HistoricOperationEvent; import org.cloudfoundry.multiapps.controller.persistence.model.ImmutableHistoricOperationEvent; import org.cloudfoundry.multiapps.controller.persistence.model.LoggingConfiguration; -import org.cloudfoundry.multiapps.controller.persistence.services.cloudlogging.CloudLoggingServiceConfigurationService; import org.cloudfoundry.multiapps.controller.persistence.services.DescriptorBackupService; import org.cloudfoundry.multiapps.controller.persistence.services.FileService; import org.cloudfoundry.multiapps.controller.persistence.services.FileStorageException; import org.cloudfoundry.multiapps.controller.persistence.services.HistoricOperationEventService; -import org.cloudfoundry.multiapps.controller.core.cloudlogging.CloudLoggingServiceClient; import org.cloudfoundry.multiapps.controller.persistence.services.OperationService; +import org.cloudfoundry.multiapps.controller.persistence.services.cloudlogging.CloudLoggingServiceConfigurationService; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.dynatrace.DynatraceProcessDuration; import org.cloudfoundry.multiapps.controller.process.dynatrace.DynatracePublisher; @@ -213,7 +213,9 @@ private void deleteCloudLoggingServiceConfiguration(DelegateExecution execution) ProcessType processType = processTypeParser.getProcessType(execution); if (processType.equals(ProcessType.UNDEPLOY)) { - cloudLoggingServiceConfigurationService.createQuery().id(loggingConfiguration.getId()).delete(); + cloudLoggingServiceConfigurationService.createQuery() + .id(loggingConfiguration.getId()) + .delete(); cloudLoggingServiceConfigurationAuditLog.logDeleteLoggingConfiguration(VariableHandling.get(execution, Variables.USER), VariableHandling.get(execution, Variables.SPACE_GUID), loggingConfiguration); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClientTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClientTest.java index 27fd087ce1..8f9d46421c 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClientTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClientTest.java @@ -4,7 +4,7 @@ import java.util.Optional; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.ApplicationServicesUpdateCallback; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java index 78f20b5f65..fe6156d9c8 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java @@ -227,7 +227,6 @@ private void prepareClient(CloudApplication application, Set routes, when(client.getApplicationEnvironment(application.getGuid())).thenReturn(env); application = prepareAppWithStaging(application, staging); when(client.getApplication(application.getName(), false)).thenReturn(application); - when(client.getApplicationSshEnabled(application.getGuid())).thenReturn(staging.isSshEnabled() != null && staging.isSshEnabled()); when(client.getApplicationRoutes(application.getGuid())).thenReturn(List.copyOf(routes)); String command = staging.getCommand() == null ? DEFAULT_COMMAND : staging.getCommand(); var hcType = staging.getHealthCheckType(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStepTest.java index 51fe4a72c7..cfa0657540 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStepTest.java @@ -6,13 +6,13 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java index 96a150b2fc..7413431144 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java @@ -2,8 +2,8 @@ import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -33,8 +33,8 @@ private CloudServiceInstanceExtended buildCloudServiceInstanceExtended() { return ImmutableCloudServiceInstanceExtended.builder() .name(SERVICE_INSTANCE_NAME) .metadata(ImmutableCloudMetadata.of(SERVICE_INSTANCE_GUID)) - .v3Metadata(Metadata.builder() - .annotation("key", "value") + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation("key", "value") .build()) .build(); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java index 6dc70e3ff9..8085e30558 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java @@ -5,10 +5,11 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; @@ -61,14 +62,14 @@ private void validateServicesDetached(List serviceKeysToD } private static Metadata getMetadataWithoutMtaFields() { - return Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, null) - .label(MtaMetadataLabels.MTA_NAMESPACE, null) - .label(MtaMetadataLabels.SPACE_GUID, null) - .annotation(MtaMetadataAnnotations.MTA_ID, null) - .annotation(MtaMetadataAnnotations.MTA_VERSION, null) - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, null) - .annotation(MtaMetadataLabels.MTA_NAMESPACE, null) + return ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, null) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, null) + .putLabel(MtaMetadataLabels.SPACE_GUID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, null) + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, null) + .putAnnotation(MtaMetadataLabels.MTA_NAMESPACE, null) .build(); } @@ -87,14 +88,14 @@ private static DeployedMtaServiceKey createDeployedServiceKeyWithoutMetadata(Str private static DeployedMtaServiceKey createDeployedServiceKey(String name, boolean generateMetadata) { Metadata v3Metadata; if (generateMetadata) { - v3Metadata = Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") - .annotation(MtaMetadataAnnotations.MTA_ID, "test") - .annotation(MtaMetadataAnnotations.MTA_VERSION, "1") - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") + v3Metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") + .putAnnotation(MtaMetadataAnnotations.MTA_ID, "test") + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") .build(); } else { - v3Metadata = Metadata.builder() + v3Metadata = ImmutableMetadata.builder() .labels(Collections.emptyMap()) .annotations(Collections.emptyMap()) .build(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java index 40b15d83d3..169c20d8ef 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java @@ -6,11 +6,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -53,14 +54,14 @@ private void validateServicesDetached(List servicesToDeta } private static Metadata getMetadataWithoutMtaFields() { - return Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, null) - .label(MtaMetadataLabels.MTA_NAMESPACE, null) - .label(MtaMetadataLabels.SPACE_GUID, null) - .annotation(MtaMetadataAnnotations.MTA_ID, null) - .annotation(MtaMetadataAnnotations.MTA_VERSION, null) - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, null) - .annotation(MtaMetadataLabels.MTA_NAMESPACE, null) + return ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, null) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, null) + .putLabel(MtaMetadataLabels.SPACE_GUID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, null) + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, null) + .putAnnotation(MtaMetadataLabels.MTA_NAMESPACE, null) .build(); } @@ -88,11 +89,11 @@ private void prepareClient(List servicesToDelete) { } private CloudServiceInstance createServiceInstance(SimpleServiceInstance serviceInstance) { - Metadata v3Metadata = Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") - .annotation(MtaMetadataAnnotations.MTA_ID, "test") - .annotation(MtaMetadataAnnotations.MTA_VERSION, "1") - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") + Metadata v3Metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") + .putAnnotation(MtaMetadataAnnotations.MTA_ID, "test") + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") .build(); CloudMetadata metadata = ImmutableCloudMetadata.builder() .guid(serviceInstance.guid) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStepTest.java index 9d9c09c8db..6ce1f876e1 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStepTest.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.Optional; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.test.Tester.Expectation; import org.cloudfoundry.multiapps.common.util.JsonUtil; @@ -99,8 +99,8 @@ void testExecuteWithBackupdMta() { .addApplication(ImmutableDeployedMtaApplication.builder() .moduleName("test-module") .name("test-app") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION_2) .build()) @@ -116,8 +116,8 @@ void testExecuteWithBackupdMta() { .addApplication(ImmutableDeployedMtaApplication.builder() .moduleName("test-module") .name("mta-backup-test-app") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION_1) .build()) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStepTest.java index 069619f4e8..62a2cad73e 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStepTest.java @@ -8,13 +8,13 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java index f04e7ca7b6..9d8a8ffc32 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java @@ -5,12 +5,12 @@ import java.util.Map; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; @@ -102,8 +102,8 @@ private CloudServiceInstanceExtended buildOptionalCloudServiceInstance() { return ImmutableCloudServiceInstanceExtended.builder() .isOptional(true) .name(SERVICE_TO_DELETE) - .v3Metadata(Metadata.builder() - .label("label-key", "label-value") + .v3Metadata(ImmutableMetadata.builder() + .putLabel("label-key", "label-value") .build()) .build(); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java index 82da244ea5..9e8a328d58 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java @@ -5,12 +5,12 @@ import java.util.Map; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; @@ -125,11 +125,11 @@ void testUpdateUnchangedServiceKeyMetadata() { CloudServiceInstanceExtended serviceInstance = buildCloudServiceInstanceExtended(false); CloudServiceKey serviceKeyToUpdate = buildCloudServiceKey(); CloudServiceKey existingVersionOfKey = ImmutableCloudServiceKey.copyOf(serviceKeyToUpdate) - .withV3Metadata(Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, "aaaaa") - .annotation(MtaMetadataAnnotations.MTA_ID, + .withV3Metadata(ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, "aaaaa") + .putAnnotation(MtaMetadataAnnotations.MTA_ID, "different-test-mta") - .annotation( + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION) .build()); @@ -156,10 +156,10 @@ private CloudServiceKey buildCloudServiceKey() { return ImmutableCloudServiceKey.builder() .name(SERVICE_KEY_NAME) .metadata(ImmutableCloudMetadata.of(SERVICE_KEY_GUID)) - .v3Metadata(Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, MTA_ID_HASH) - .annotation(MtaMetadataAnnotations.MTA_ID, MTA_ID) - .annotation(MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION) + .v3Metadata(ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, MTA_ID_HASH) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, MTA_ID) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION) .build()) .serviceKeyOperation(ImmutableServiceCredentialBindingOperation.builder() .type( diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStepTest.java index 1a1384edb2..4f8d239545 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStepTest.java @@ -9,8 +9,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.common.ContentException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.core.cf.v2.ResourceType; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOperationExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOperationExecutionTest.java index c4c30ded04..c4304b6b11 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOperationExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOperationExecutionTest.java @@ -3,10 +3,10 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.junit.jupiter.api.Test; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokerOperationsExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokerOperationsExecutionTest.java index fb82e84353..cafb3e24bd 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokerOperationsExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokerOperationsExecutionTest.java @@ -3,8 +3,8 @@ import java.util.List; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; import org.cloudfoundry.multiapps.controller.process.variables.Variables; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokersOperationsExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokersOperationsExecutionTest.java index 94760131fd..c00b848696 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokersOperationsExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokersOperationsExecutionTest.java @@ -5,8 +5,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceKeyCreationOperationExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceKeyCreationOperationExecutionTest.java index a3b563ae2c..0836b1c072 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceKeyCreationOperationExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceKeyCreationOperationExecutionTest.java @@ -4,8 +4,8 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceUnbindingsOperationExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceUnbindingsOperationExecutionTest.java index 671c01c888..e1ad3e6065 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceUnbindingsOperationExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceUnbindingsOperationExecutionTest.java @@ -2,13 +2,13 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.jobs.JobState; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java index ee1671799e..e5b8211ab6 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java @@ -3,8 +3,8 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; @@ -45,10 +45,10 @@ void testStep(String applicationName, String namespace, String expectedNewApplic assertStepFinishedSuccessfully(); verify(client).rename(applicationName, expectedNewApplicationName); - verify(client).updateApplicationMetadata(applicationGuid, Metadata.builder() - .label(MtaMetadataLabels.MTA_NAMESPACE, + verify(client).updateApplicationMetadata(applicationGuid, ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, MtaMetadataUtil.getHashedLabel(expectedNamespace)) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, expectedNamespace) .build()); } @@ -59,7 +59,7 @@ private void prepareContext(String applicationName, String namespace, UUID appli .metadata(ImmutableCloudMetadata.builder() .guid(applicationGuid) .build()) - .v3Metadata(Metadata.builder() + .v3Metadata(ImmutableMetadata.builder() .build()) .build(); context.setVariable(Variables.APP_TO_PROCESS, application); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java index 861a639057..83cdd3eb13 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java @@ -13,7 +13,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.common.ContentException; import org.cloudfoundry.multiapps.controller.core.cf.detect.DeployedMtaDetector; import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; @@ -129,8 +129,8 @@ private List createDeployedMtaApplications(List .map(testApp -> ImmutableDeployedMtaApplication.builder() .name(testApp.appName) .moduleName(testApp.appName) - .v3Metadata(Metadata.builder() - .annotation(MtaMetadataAnnotations.MTA_VERSION, + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, testApp.version) .build()) .build()) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java index 3d67e6d51f..7f1a47bc6f 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java @@ -2,8 +2,8 @@ import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; @@ -34,9 +34,9 @@ void testRemoveSystemNamespace() { step.execute(execution); assertStepFinishedSuccessfully(); - verify(client).updateApplicationMetadata(APP_GUID, Metadata.builder() - .label(MtaMetadataLabels.MTA_NAMESPACE, null) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, null) + verify(client).updateApplicationMetadata(APP_GUID, ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, null) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, null) .build()); } @@ -50,10 +50,10 @@ void testRemoveSystemNamespaceAndKeepUserNamespace() { step.execute(execution); assertStepFinishedSuccessfully(); - verify(client).updateApplicationMetadata(APP_GUID, Metadata.builder() - .label(MtaMetadataLabels.MTA_NAMESPACE, + verify(client).updateApplicationMetadata(APP_GUID, ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, MtaMetadataUtil.getHashedLabel(userNamespace)) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, userNamespace) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, userNamespace) .build()); } @@ -62,11 +62,11 @@ private DeployedMta createBackupMta(String namespace) { .addApplication(ImmutableDeployedMtaApplication.builder() .moduleName(MODULE_NAME) .name(BACKUP_APP_NAME) - .v3Metadata(Metadata.builder() - .label( + .v3Metadata(ImmutableMetadata.builder() + .putLabel( MtaMetadataLabels.MTA_NAMESPACE, namespace) - .annotation( + .putAnnotation( MtaMetadataAnnotations.MTA_NAMESPACE, namespace) .build()) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java index 53e1e33fb0..2d5a604930 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java @@ -3,7 +3,8 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; @@ -34,8 +35,8 @@ public static void prepareDisablingAutoscaler(ProcessContext context, CloudContr } public static void testIfEnabledOrDisabledAutoscaler(CloudControllerClient client, String labelValue, UUID uid) { - Metadata metadata = Metadata.builder() - .label(MtaMetadataLabels.AUTOSCALER_LABEL, labelValue) + Metadata metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.AUTOSCALER_LABEL, labelValue) .build(); verify(client).updateApplicationMetadata(uid, metadata); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java index 59ceec125e..aec0b06039 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java @@ -8,10 +8,10 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.common.test.Tester; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; @@ -194,8 +194,8 @@ protected CloudServiceInstanceExtended buildServiceToProcess(boolean isOptional) .guid(UUID.randomUUID()) .build()) .syslogDrainUrl(SYSLOG_DRAIN_URL) - .v3Metadata(Metadata.builder() - .label(METADATA_LABEL, METADATA_LABEL_VALUE) + .v3Metadata(ImmutableMetadata.builder() + .putLabel(METADATA_LABEL, METADATA_LABEL_VALUE) .build()) .isOptional(isOptional) .build(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java index 1533a3af32..ae69c46938 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java @@ -5,12 +5,13 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.process.Messages; @@ -64,14 +65,14 @@ void testUpdateServiceMetadataFailure() { private CloudServiceKey buildTestServiceKey(boolean generateMetadata) { Metadata v3Metadata; if (generateMetadata) { - v3Metadata = Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") - .annotation(MtaMetadataAnnotations.MTA_ID, "test") - .annotation(MtaMetadataAnnotations.MTA_VERSION, "1") - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") + v3Metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") + .putAnnotation(MtaMetadataAnnotations.MTA_ID, "test") + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") .build(); } else { - v3Metadata = Metadata.builder() + v3Metadata = ImmutableMetadata.builder() .labels(Collections.emptyMap()) .annotations(Collections.emptyMap()) .build(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java index 1147c87e67..ba262b726a 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java @@ -7,9 +7,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; @@ -66,8 +66,8 @@ private static Stream testCalculateExistingAppsToBackup() { Collections.emptyList(), "2", true, List.of("app-1-live", "app-1-idle"), List.of(ImmutableCloudApplication.builder() .name("app-1-live") - .v3Metadata(Metadata.builder() - .annotation(MtaMetadataAnnotations.MTA_VERSION, + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") .build()) .build())), @@ -83,8 +83,8 @@ private static Stream testCalculateExistingAppsToBackup() { "3", true, List.of("app-1-live", "mta-backup-app-1"), List.of(ImmutableCloudApplication.builder() .name("app-1-live") .v3Metadata( - Metadata.builder() - .annotation( + ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, "2") .build()) @@ -106,8 +106,8 @@ private static Stream testCalculateExistingAppsToBackup() { List.of(new TestApplication("app-1", "mta-backup-app-1", "1")), "3", true, List.of("app-1-live"), List.of(ImmutableCloudApplication.builder() .name("app-1-live") - .v3Metadata(Metadata.builder() - .annotation(MtaMetadataAnnotations.MTA_VERSION, + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "2") .build()) .build())), @@ -146,8 +146,8 @@ private static Stream testCalculateAppsToUndeploy() { Arguments.of(List.of(new TestApplication("app-1", "mta-backup-app-1", "1")), List.of("app-1-idle"), true, List.of(ImmutableCloudApplication.builder() .name("mta-backup-app-1") - .v3Metadata(Metadata.builder() - .annotation(MtaMetadataAnnotations.MTA_VERSION, + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") .build()) .build())), @@ -166,16 +166,16 @@ private static Stream testCalculateAppsToUndeploy() { new TestApplication("app-2", "mta-backup-app-2", "2")), Collections.emptyList(), false, List.of(ImmutableCloudApplication.builder() .name("mta-backup-app-1") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, "2") .build()) .build(), ImmutableCloudApplication.builder() .name("mta-backup-app-2") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, "2") .build()) @@ -213,12 +213,12 @@ private DeployedMta getDeployedMta(List deployedApplications) { deployedMtaApplications.add(ImmutableDeployedMtaApplication.builder() .moduleName(application.moduleName) .name(application.appName) - .v3Metadata(application.mtaVersion != null ? Metadata.builder() - .annotation( + .v3Metadata(application.mtaVersion != null ? ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, application.mtaVersion) .build() - : Metadata.builder() + : ImmutableMetadata.builder() .annotations(Collections.emptyMap()) .build()) .productizationState(application.productizationState) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java index 3b9cb47816..de7d1cac2e 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java @@ -3,7 +3,8 @@ import java.util.List; import java.util.Map; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; @@ -97,7 +98,7 @@ private CloudApplicationExtended createApplication(String appName, String module } private Metadata createV3Metadata(Map labels, Map annotations) { - return Metadata.builder() + return ImmutableMetadata.builder() .labels(labels) .annotations(annotations) .build(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java index de3062a4e9..bb3fc3bd1d 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.api.model.ImmutableOperation; import org.cloudfoundry.multiapps.controller.api.model.Operation; import org.cloudfoundry.multiapps.controller.api.model.Operation.State; @@ -489,8 +489,8 @@ private DeployedMta buildDeployedMta(String mtaVersion) { DeployedMtaApplication application = ImmutableDeployedMtaApplication.builder() .name("app-1") .moduleName("module-1") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, mtaVersion) .build()) diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/upload/UploadFromUrlContext.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/upload/UploadFromUrlContext.java index c1ef04215d..18134dd785 100644 --- a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/upload/UploadFromUrlContext.java +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/upload/UploadFromUrlContext.java @@ -4,7 +4,7 @@ import java.util.concurrent.atomic.AtomicLong; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.cloudfoundry.Nullable; +import org.cloudfoundry.multiapps.controller.client.facade.Nullable; import org.cloudfoundry.multiapps.controller.api.model.UserCredentials; import org.cloudfoundry.multiapps.controller.persistence.model.AsyncUploadJobEntry; import org.immutables.value.Value; diff --git a/pom.xml b/pom.xml index da9c54a4ea..99f7358fb3 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,6 @@ 2.22.3 4.33.0 5.1.0 - 5.16.0.RELEASE 1.3.7 4.2.18.Final 1.6.16 @@ -64,7 +63,7 @@ 12.35.0 1.0.4 4.0.1 - 6.4.7 + 2.2 8.20.0 @@ -408,6 +407,20 @@ micrometer-registry-jmx ${micrometer.version} + + + org.springframework + spring-test + ${spring.version} + test + + + + org.hamcrest + hamcrest + ${hamcrest.version}} + test + io.swagger @@ -832,13 +845,6 @@ bucket4j_jdk17-postgresql ${bucket4j.version} - - - - com.squareup.wire - wire-runtime - ${wire-runtime.version} - org.bouncycastle @@ -857,12 +863,6 @@ java-cfenv-jdbc ${java-cfenv.version} - - - org.cloudfoundry - cloudfoundry-client-reactor - ${cloudfoundry-client.version} -