
com.vmware.photon.controller.model.adapters.util.EndpointAdapterUtils Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2015-2016 VMware, Inc. All Rights Reserved.
*
* 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.vmware.photon.controller.model.adapters.util;
import static com.vmware.photon.controller.model.adapters.util.AdapterConstants.PHOTON_MODEL_ADAPTER_ENDPOINT_NOT_UNIQUE_MESSAGE;
import static com.vmware.photon.controller.model.adapters.util.AdapterConstants.PHOTON_MODEL_ADAPTER_ENDPOINT_NOT_UNIQUE_MESSAGE_CODE;
import static com.vmware.photon.controller.model.util.ClusterUtil.ServiceTypeCluster.INVENTORY_SERVICE;
import static com.vmware.photon.controller.model.util.PhotonModelUriUtils.createInventoryUri;
import static com.vmware.xenon.common.UriUtils.buildUri;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest;
import com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest.RequestType;
import com.vmware.photon.controller.model.adapters.registry.PhotonModelAdaptersRegistryService;
import com.vmware.photon.controller.model.adapters.registry.PhotonModelAdaptersRegistryService.PhotonModelAdapterConfig;
import com.vmware.photon.controller.model.constants.PhotonModelConstants.EndpointType;
import com.vmware.photon.controller.model.query.QueryUtils.QueryTop;
import com.vmware.photon.controller.model.resources.ComputeDescriptionService.ComputeDescription;
import com.vmware.photon.controller.model.resources.ComputeService.ComputeState;
import com.vmware.photon.controller.model.resources.ComputeService.PowerState;
import com.vmware.photon.controller.model.resources.EndpointService.EndpointState;
import com.vmware.photon.controller.model.util.ClusterUtil;
import com.vmware.photon.controller.model.util.ServiceEndpointLocator;
import com.vmware.xenon.common.DeferredResult;
import com.vmware.xenon.common.LocalizableValidationException;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.OperationJoin;
import com.vmware.xenon.common.ServiceErrorResponse;
import com.vmware.xenon.common.ServiceHost;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.services.common.AuthCredentialsService.AuthCredentialsServiceState;
import com.vmware.xenon.services.common.QueryTask.Query;
import com.vmware.xenon.services.common.QueryTask.Query.Builder;
public class EndpointAdapterUtils {
public static final String MOCK_REQUEST = "mockRequest";
public static final String ENDPOINT_REFERENCE_URI = "endpointReferenceUri";
/**
* see {@link #registerEndpointAdapters(ServiceHost, EndpointType, String[], Map, ServiceEndpointLocator)}
*/
public static void registerEndpointAdapters(
ServiceHost host,
EndpointType endpointType,
String[] startedAdapterLinks,
Map adapterLinksToRegister) {
registerEndpointAdapters(host, endpointType, startedAdapterLinks, adapterLinksToRegister,
null);
}
/**
* Register end-point adapters into End-point Adapters Registry.
*
* @param host
* The host the end-point is running on.
* @param endpointType
* The type of the end-point.
* @param startedAdapterLinks
* The array of started adapter links.
* @param adapterLinksToRegister
* Map of adapter links (to be registered) to their adapter type key. e.g for
* standard adapters this is {@link com.vmware.photon.controller.model.UriPaths.AdapterTypePath#key}
* @param registryLocator
* ServiceEndpointLocator containing the host of the adapter registry. Can be null if the
* registry is on the same host.
* @see #handleEndpointRegistration(ServiceHost, EndpointType, Consumer, ServiceEndpointLocator)
*/
public static void registerEndpointAdapters(
ServiceHost host,
EndpointType endpointType,
String[] startedAdapterLinks,
Map adapterLinksToRegister,
ServiceEndpointLocator registryLocator) {
// Count all adapters - both FAILED and STARTED
AtomicInteger adaptersCountDown = new AtomicInteger(startedAdapterLinks.length);
// Keep started adapters only...
// - key = adapter type ket (e.g. AdapterTypePath.key)
// - value = adapter URI
Map startedAdapters = new ConcurrentHashMap<>();
// Wait for all adapter services to start
host.registerForServiceAvailability((op, ex) -> {
if (ex != null) {
//When xenon is in cluster the operation might be null if there is an exception
String adapterPath = Optional.ofNullable(op)
.map(Operation::getUri)
.map(URI::getPath)
.orElse("");
host.log(Level.WARNING, "Starting '%s' adapter [%s]: FAILED - %s",
endpointType, adapterPath, Utils.toString(ex));
} else {
String adapterPath = op.getUri().getPath();
host.log(Level.FINE, "Starting '%s' adapter [%s]: SUCCESS",
endpointType, adapterPath);
String adapterKey = adapterLinksToRegister.get(adapterPath);
if (adapterKey != null) {
startedAdapters.put(
adapterKey,
AdapterUriUtil.buildPublicAdapterUri(host, adapterPath).toString());
}
}
if (adaptersCountDown.decrementAndGet() == 0) {
// Once ALL Adapters are started register them into End-point Adapters Registry
host.log(Level.INFO, "Starting %d '%s' adapters: SUCCESS",
startedAdapters.size(), endpointType);
// Populate end-point config with started adapters
Consumer endpointConfigEnhancer = ep -> ep.adapterEndpoints
.putAll(startedAdapters);
// Delegate to core end-point config/registration logic
handleEndpointRegistration(
host, endpointType, endpointConfigEnhancer, registryLocator);
}
}, /* this services are not replicated */ false, startedAdapterLinks);
}
/**
* Enhance end-point config with all adapters that are to be published/registered to End-point
* Adapters Registry.
*
* @param host
* The host the end-point is running on.
* @param endpointType
* The type of the end-point.
* @param endpointConfigEnhancer
* Optional {@link PhotonModelAdapterConfig} enhance logic specific to the end-point
* type. The config passed to the callback is pre-populated with id, name and
* documentSelfLink (all set with {@code endpointType} param). The enhancer might
* populate the config with the links of end-point adapters. Once enhanced the config is
* posted to the {@link PhotonModelAdaptersRegistryService Adapters Registry}.
*/
public static void handleEndpointRegistration(
ServiceHost host,
EndpointType endpointType,
Consumer endpointConfigEnhancer,
ServiceEndpointLocator registryLocator) {
// If registry locator is set we assume the service is started
if (registryLocator != null && registryLocator.getUri() != null) {
registerEndpoint(host, endpointType, endpointConfigEnhancer, registryLocator);
} else {
host.registerForServiceAvailability((op, ex) -> {
//Once End-point Adapters Registry is available register end-point adapters
if (ex != null) {
host.log(Level.WARNING,
"End-point Adapters Registry is not available on this host. Please ensure %s is started.",
PhotonModelAdaptersRegistryService.class.getSimpleName());
return;
}
registerEndpoint(host, endpointType, endpointConfigEnhancer, registryLocator);
}, true, PhotonModelAdaptersRegistryService.FACTORY_LINK);
}
}
private static void registerEndpoint(
ServiceHost host,
EndpointType endpointType,
Consumer endpointConfigEnhancer,
ServiceEndpointLocator registryLocator) {
PhotonModelAdapterConfig endpointConfig = new PhotonModelAdapterConfig();
// By contract the id MUST equal to endpointType
endpointConfig.id = endpointType.name();
endpointConfig.documentSelfLink = endpointConfig.id;
endpointConfig.name = endpointType.toString();
endpointConfig.adapterEndpoints = new HashMap<>();
if (endpointConfigEnhancer != null) {
// Pass to enhancer to customize the end-point config.
endpointConfigEnhancer.accept(endpointConfig);
}
URI uri = buildUri(ClusterUtil.getClusterUri(host, registryLocator),
PhotonModelAdaptersRegistryService.FACTORY_LINK);
Operation postEndpointConfigOp = Operation.createPost(uri)
.setReferer(host.getUri())
.setBody(endpointConfig);
host.sendWithDeferredResult(postEndpointConfigOp).whenComplete((o, e) -> {
if (e != null) {
host.log(Level.WARNING,
"Registering %d '%s' adapters into End-point Adapters Registry: FAILED - %s",
endpointConfig.adapterEndpoints.size(), endpointType,
Utils.toString(e));
} else {
host.log(Level.INFO,
"Registering %d '%s' adapters into End-point Adapters Registry: SUCCESS",
endpointConfig.adapterEndpoints.size(), endpointType);
}
});
}
public static void handleEndpointRequest(StatelessService service, Operation op,
EndpointConfigRequest body,
BiConsumer credEnhancer,
BiConsumer descEnhancer,
BiConsumer compEnhancer,
BiConsumer endpointEnhancer,
BiConsumer> validator) {
switch (body.requestType) {
case VALIDATE:
if (body.isMockRequest) {
op.complete();
} else {
validate(service, op, body, credEnhancer, validator);
}
break;
case ENHANCE:
op.complete();
configureEndpoint(service, body, credEnhancer, descEnhancer, compEnhancer,
endpointEnhancer);
break;
case CHECK_IF_ACCOUNT_EXISTS:
op.complete();
break;
default:
op.fail(new IllegalArgumentException(
"Unexpected endpoint request: " + body.requestType.toString()));
}
}
private static void configureEndpoint(StatelessService service, EndpointConfigRequest body,
BiConsumer credEnhancer,
BiConsumer descEnhancer,
BiConsumer compEnhancer,
BiConsumer endpointEnhancer) {
TaskManager tm = new TaskManager(service, body.taskReference, body.resourceLink());
Consumer onFailure = tm::patchTaskToFailure;
Consumer onSuccess = (op) -> {
EndpointState endpoint = op.getBody(EndpointState.class);
op.complete();
AuthCredentialsServiceState authState = new AuthCredentialsServiceState();
Map props = new HashMap<>(body.endpointProperties);
props.put(MOCK_REQUEST, String.valueOf(body.isMockRequest));
props.put(ENDPOINT_REFERENCE_URI, body.resourceReference.toString());
Retriever r = Retriever.of(props);
try {
credEnhancer.accept(authState, r);
ComputeDescription cd = new ComputeDescription();
descEnhancer.accept(cd, r);
ComputeState cs = new ComputeState();
cs.powerState = PowerState.ON;
compEnhancer.accept(cs, r);
EndpointState es = new EndpointState();
es.endpointProperties = new HashMap<>();
es.regionId = r.get(EndpointConfigRequest.REGION_KEY).orElse(null);
endpointEnhancer.accept(es, r);
Stream operations = Stream.of(
Pair.of(authState, endpoint.authCredentialsLink),
Pair.of(cd, endpoint.computeDescriptionLink),
Pair.of(cs, endpoint.computeLink),
Pair.of(es, endpoint.documentSelfLink))
.map((p) -> Operation
.createPatch(createInventoryUri(service.getHost(), p.right))
.setBody(p.left)
.setReferer(service.getUri()));
applyChanges(tm, service, endpoint, operations);
} catch (Exception e) {
tm.patchTaskToFailure(e);
}
};
AdapterUtils.getServiceState(service, body.resourceReference, onSuccess, onFailure);
}
private static void applyChanges(TaskManager tm, StatelessService service,
EndpointState endpoint, Stream operations) {
OperationJoin joinOp = OperationJoin.create(operations);
joinOp.setCompletion((ox, exc) -> {
if (exc != null) {
service.logSevere(
"Error patching endpoint configuration data for %s. %s",
endpoint.endpointType,
Utils.toString(exc));
tm.patchTaskToFailure(exc.values().iterator().next());
return;
}
service.logFine(
() -> String.format("Successfully completed %s endpoint configuration tasks.",
endpoint.endpointType));
tm.finishTask();
});
joinOp.sendWith(service);
}
private static void validate(StatelessService service, Operation op,
EndpointConfigRequest configRequest,
BiConsumer enhancer,
BiConsumer> validator) {
Consumer onSuccessGetCredentials = oc -> {
try {
AuthCredentialsServiceState credentials = oc
.getBody(AuthCredentialsServiceState.class);
enhancer.accept(credentials, Retriever.of(configRequest.endpointProperties));
BiConsumer callback = (r, e) -> {
service.logInfo("Finished validating credentials for operation: %d",
op.getId());
if (r == null && e == null) {
if (configRequest.requestType == RequestType.VALIDATE) {
op.complete();
}
} else {
op.fail(e, r);
}
};
service.logInfo("Validating credentials for operation: %d", op.getId());
validator.accept(credentials, callback);
} catch (Throwable e) {
op.fail(e);
}
};
Consumer onSuccessGetEndpoint = o -> {
EndpointState endpointState = o.getBody(EndpointState.class);
if (endpointState.authCredentialsLink != null) {
AdapterUtils.getServiceState(service,
createInventoryUri(service.getHost(), endpointState.authCredentialsLink),
onSuccessGetCredentials, op::fail);
} else {
onSuccessGetCredentials.accept(getEmptyAuthCredentialState(configRequest));
}
};
// if there is an endpoint, get it and then get the credentials
if (configRequest.resourceReference != null) {
// If there is an error getting endpoint state, we assume that endpoint is not yet
// created, but it was requested with a predefined link
AdapterUtils.getServiceState(service, configRequest.resourceReference,
onSuccessGetEndpoint,
e -> onSuccessGetCredentials
.accept(getEmptyAuthCredentialState(configRequest)));
} else { // otherwise, proceed with empty credentials and rely on what's in
// endpointProperties
onSuccessGetCredentials.accept(getEmptyAuthCredentialState(configRequest));
}
}
private static Operation getEmptyAuthCredentialState(EndpointConfigRequest configRequest) {
AuthCredentialsServiceState authCredentials = new AuthCredentialsServiceState();
if (configRequest.tenantLinks != null) {
authCredentials.tenantLinks = configRequest.tenantLinks;
}
return new Operation().setBody(authCredentials);
}
public static class Retriever {
final Map values;
private Retriever(Map values) {
this.values = values;
}
public static Retriever of(Map values) {
return new Retriever(values);
}
public Optional get(String key) {
return Optional.ofNullable(this.values.get(key));
}
public String getRequired(String key) {
return get(key).orElseThrow(
() -> new IllegalArgumentException(String.format("%s is required", key)));
}
}
/**
* Validates that no endpoint exists that have the same credentials and identifier.
*
* @param host
* The host to use to query the photon-model
* @param authQuery
* (Optional) Query used to express the criteria for @{@link
* AuthCredentialsServiceState}
* @param endpointQuery
* (Optional) Query used to express the criteria for @{@link EndpointState}
* @param endpointType
* The endpoint type of the adapter
* @param queryTaskTenantLinks
* The tenantLinks used for creating a QueryTask
* @return A void DeferredResult when validation is successful and a failed DeferredResult when
* validation is not successful
*/
public static DeferredResult validateEndpointUniqueness(ServiceHost host, Query authQuery,
Query endpointQuery, String endpointType, List queryTaskTenantLinks) {
return getAuthLinks(host, authQuery, queryTaskTenantLinks)
.thenCompose(links -> getEndpointLinks(host,
endpointQuery, links, endpointType, queryTaskTenantLinks))
.thenCompose(EndpointAdapterUtils::verifyLinks)
.thenApply(ignore -> null);
}
private static DeferredResult> getAuthLinks(ServiceHost host, Query
authQuery, List queryTaskTenantLinks) {
Query.Builder authQueryBuilder = Builder.create()
.addKindFieldClause(AuthCredentialsServiceState.class);
if (authQuery != null) {
authQueryBuilder.addClause(authQuery);
}
QueryTop queryAuth = new QueryTop<>(
host,
authQueryBuilder.build(),
AuthCredentialsServiceState.class,
queryTaskTenantLinks)
.setQueryTaskTenantLinks(queryTaskTenantLinks);
queryAuth.setClusterType(INVENTORY_SERVICE);
return queryAuth.collectLinks(Collectors.toList());
}
private static DeferredResult> getEndpointLinks(ServiceHost host, Query
endpointQuery, List credentialsLinks, String endpointType, List
queryTaskTenantLinks) {
if (credentialsLinks.isEmpty()) {
return DeferredResult.completed(Collections.emptyList());
}
Query.Builder qBuilder = Builder.create()
.addKindFieldClause(EndpointState.class)
.addFieldClause(EndpointState.FIELD_NAME_ENDPOINT_TYPE, endpointType)
.addInClause(EndpointState.FIELD_NAME_AUTH_CREDENTIALS_LINK, credentialsLinks);
if (endpointQuery != null) {
qBuilder.addClause(endpointQuery);
}
QueryTop queryEndpoints = new QueryTop<>(
host,
qBuilder.build(),
EndpointState.class,
queryTaskTenantLinks)
.setQueryTaskTenantLinks(queryTaskTenantLinks)
.setMaxResultsLimit(1);
queryEndpoints.setClusterType(INVENTORY_SERVICE);
return queryEndpoints.collectLinks(Collectors.toList());
}
private static DeferredResult verifyLinks(List links) {
if (!links.isEmpty()) {
return DeferredResult.failed(
new LocalizableValidationException(
PHOTON_MODEL_ADAPTER_ENDPOINT_NOT_UNIQUE_MESSAGE,
PHOTON_MODEL_ADAPTER_ENDPOINT_NOT_UNIQUE_MESSAGE_CODE));
}
return DeferredResult.completed(null);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy