com.netflix.spinnaker.clouddriver.cloudfoundry.client.ServiceInstances Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2018 Pivotal, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.netflix.spinnaker.clouddriver.cloudfoundry.client;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClientUtils.*;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v2.ConfigFeatureFlag.ConfigFlag.SERVICE_INSTANCE_SHARING;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v2.LastOperation.State.*;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v2.LastOperation.Type.*;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v2.ServiceInstance.Type.MANAGED_SERVICE_INSTANCE;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v2.ServiceInstance.Type.USER_PROVIDED_SERVICE_INSTANCE;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.isBlank;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.ConfigService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.ServiceInstanceService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.ServiceInstanceResponse;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v2.*;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.CreateSharedServiceInstances;
import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundryService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundryServiceInstance;
import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundryServicePlan;
import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundrySpace;
import java.io.IOException;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import okhttp3.ResponseBody;
import org.springframework.util.StringUtils;
import retrofit2.Call;
@RequiredArgsConstructor
public class ServiceInstances {
private final ServiceInstanceService api;
private final ConfigService configApi;
private final Spaces spaces;
public List> findAllServiceBindingsByServiceName(
String region, String serviceName) {
CloudFoundryServiceInstance serviceInstance = getServiceInstance(region, serviceName);
if (serviceInstance == null) {
return emptyList();
}
return findAllServiceBindingsByService(serviceInstance.getId());
}
public void createServiceBinding(CreateServiceBinding createServiceBinding) {
try {
safelyCall(() -> api.createServiceBinding(createServiceBinding));
} catch (CloudFoundryApiException e) {
if (e.getErrorCode() == null) throw e;
switch (e.getErrorCode()) {
case SERVICE_INSTANCE_ALREADY_BOUND:
return;
default:
throw e;
}
}
}
public List> findAllServiceBindingsByApp(String appGuid) {
String bindingsQuery = "app_guid:" + appGuid;
return collectPageResources(
"service bindings", pg -> api.getAllServiceBindings(singletonList(bindingsQuery)));
}
public List> findAllServiceBindingsByService(String serviceGuid) {
String bindingsQuery = "service_instance_guid:" + serviceGuid;
return collectPageResources(
"service bindings", pg -> api.getAllServiceBindings(singletonList(bindingsQuery)));
}
public void deleteServiceBinding(String serviceBindingGuid) {
safelyCall(() -> api.deleteServiceBinding(serviceBindingGuid));
}
private Resource findServiceByServiceName(String serviceName) {
List> services =
collectPageResources(
"services by name", pg -> api.findService(pg, singletonList("label:" + serviceName)));
return ofNullable(services.get(0)).orElse(null);
}
private List findAllServicePlansByServiceName(String serviceName) {
Resource service = findServiceByServiceName(serviceName);
List> services =
collectPageResources(
"service plans by id",
pg ->
api.findServicePlans(
pg, singletonList("service_guid:" + service.getMetadata().getGuid())));
return services.stream()
.map(
resource ->
CloudFoundryServicePlan.builder()
.name(resource.getEntity().getName())
.id(resource.getMetadata().getGuid())
.build())
.collect(toList());
}
public List findAllServicesByRegion(String region) {
return spaces
.findSpaceByRegion(region)
.map(
space -> {
List> services =
collectPageResources(
"all service", pg -> api.findServiceBySpaceId(space.getId(), pg, null));
return services.stream()
.map(
serviceResource ->
CloudFoundryService.builder()
.name(serviceResource.getEntity().getLabel())
.servicePlans(
findAllServicePlansByServiceName(
serviceResource.getEntity().getLabel()))
.build())
.collect(toList());
})
.orElse(Collections.emptyList());
}
public List> findAllServicesBySpaceAndNames(
CloudFoundrySpace space, List serviceInstanceNames) {
if (serviceInstanceNames == null || serviceInstanceNames.isEmpty()) return emptyList();
List serviceInstanceQuery = getServiceQueryParams(serviceInstanceNames, space);
List> serviceInstances = new ArrayList<>();
serviceInstances.addAll(
collectPageResources("service instances", pg -> api.all(pg, serviceInstanceQuery)));
serviceInstances.addAll(
collectPageResources(
"service instances", pg -> api.allUserProvided(pg, serviceInstanceQuery)));
return serviceInstances;
}
public List>
findAllVersionedServiceInstancesBySpaceAndName(
CloudFoundrySpace space, String serviceInstanceName) {
List serviceInstanceQuery =
Arrays.asList(
"name>=" + serviceInstanceName,
"organization_guid:" + space.getOrganization().getId(),
"space_guid:" + space.getId());
List> serviceInstances = new ArrayList<>();
serviceInstances.addAll(
collectPageResources("service instances", pg -> api.all(pg, serviceInstanceQuery)));
serviceInstances.addAll(
collectPageResources(
"service instances", pg -> api.allUserProvided(pg, serviceInstanceQuery)));
return serviceInstances;
}
// Visible for testing
CloudFoundryServiceInstance getOsbServiceInstanceByRegion(
String region, String serviceInstanceName) {
CloudFoundrySpace space =
spaces
.findSpaceByRegion(region)
.orElseThrow(() -> new CloudFoundryApiException("Cannot find region '" + region + "'"));
return ofNullable(getOsbServiceInstance(space, serviceInstanceName))
.orElseThrow(
() ->
new CloudFoundryApiException(
"Cannot find service '"
+ serviceInstanceName
+ "' in region '"
+ space.getRegion()
+ "'"));
}
private Set vetSharingOfServicesArgumentsAndGetSharingSpaces(
String sharedFromRegion,
@Nullable String serviceInstanceName,
@Nullable Set sharingRegions,
String gerund) {
if (isBlank(serviceInstanceName)) {
throw new CloudFoundryApiException(
"Please specify a name for the " + gerund + " service instance");
}
sharingRegions = ofNullable(sharingRegions).orElse(Collections.emptySet());
if (sharingRegions.size() == 0) {
throw new CloudFoundryApiException(
"Please specify a list of regions for " + gerund + " '" + serviceInstanceName + "'");
}
return sharingRegions.stream()
.map(
r -> {
if (sharedFromRegion.equals(r)) {
throw new CloudFoundryApiException(
"Cannot specify 'org > space' as any of the " + gerund + " regions");
}
return spaces
.findSpaceByRegion(r)
.orElseThrow(
() ->
new CloudFoundryApiException(
"Cannot find region '" + r + "' for " + gerund));
})
.collect(toSet());
}
// Visible for testing
Set vetUnshareServiceArgumentsAndGetSharingSpaces(
@Nullable String serviceInstanceName, @Nullable Set sharingRegions) {
return vetSharingOfServicesArgumentsAndGetSharingSpaces(
"", serviceInstanceName, sharingRegions, "unsharing");
}
// Visible for testing
Set vetShareServiceArgumentsAndGetSharingSpaces(
@Nullable String sharedFromRegion,
@Nullable String serviceInstanceName,
@Nullable Set sharingRegions) {
if (isBlank(sharedFromRegion)) {
throw new CloudFoundryApiException(
"Please specify a region for the sharing service instance");
}
return vetSharingOfServicesArgumentsAndGetSharingSpaces(
sharedFromRegion, serviceInstanceName, sharingRegions, "sharing");
}
// Visible for testing
Void checkServiceShareable(
String serviceInstanceName, CloudFoundryServiceInstance serviceInstance) {
ConfigFeatureFlag featureFlag =
safelyCall(configApi::getConfigFeatureFlags).orElse(Collections.emptySet()).stream()
.filter(it -> it.getName() == SERVICE_INSTANCE_SHARING)
.findFirst()
.orElseThrow(
() ->
new CloudFoundryApiException(
"'service_instance_sharing' flag must be enabled in order to share services"));
if (!featureFlag.isEnabled()) {
throw new CloudFoundryApiException(
"'service_instance_sharing' flag must be enabled in order to share services");
}
ServicePlan plan =
safelyCall(() -> api.findServicePlanByServicePlanId(serviceInstance.getPlanId()))
.map(Resource::getEntity)
.orElseThrow(
() ->
new CloudFoundryApiException(
"The service plan for 'new-service-plan-name' was not found"));
String extraString =
safelyCall(() -> api.findServiceByServiceId(plan.getServiceGuid()))
.map(Resource::getEntity)
.map(
s ->
ofNullable(s.getExtra())
.orElseThrow(
() ->
new CloudFoundryApiException(
"The service broker must be configured as 'shareable' in order to share services")))
.orElseThrow(
() ->
new CloudFoundryApiException(
"The service broker for '" + serviceInstanceName + "' was not found"));
boolean isShareable;
try {
isShareable =
!StringUtils.isEmpty(extraString)
&& new ObjectMapper().readValue(extraString, Map.class).get("shareable")
== Boolean.TRUE;
} catch (IOException e) {
throw new CloudFoundryApiException(e);
}
if (!isShareable) {
throw new CloudFoundryApiException(
"The service broker must be configured as 'shareable' in order to share services");
}
return null;
}
public ServiceInstanceResponse shareServiceInstance(
@Nullable String region,
@Nullable String serviceInstanceName,
@Nullable Set shareToRegions) {
Set shareToSpaces =
vetShareServiceArgumentsAndGetSharingSpaces(region, serviceInstanceName, shareToRegions);
CloudFoundryServiceInstance serviceInstance =
getOsbServiceInstanceByRegion(region, serviceInstanceName);
if (MANAGED_SERVICE_INSTANCE.name().equalsIgnoreCase(serviceInstance.getType())) {
checkServiceShareable(serviceInstanceName, serviceInstance);
}
String serviceInstanceId = serviceInstance.getId();
SharedTo sharedTo =
safelyCall(() -> api.getShareServiceInstanceSpaceIdsByServiceInstanceId(serviceInstanceId))
.orElseThrow(
() ->
new CloudFoundryApiException(
"Could not fetch spaces to which '"
+ serviceInstanceName
+ "' has been shared"));
Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy