All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vmware.photon.controller.model.adapters.util.instance.BaseComputeInstanceContext 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.instance;

import static com.vmware.photon.controller.model.ComputeProperties.PLACEMENT_LINK;
import static com.vmware.photon.controller.model.util.PhotonModelUriUtils.createInventoryUri;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import com.vmware.photon.controller.model.adapterapi.ComputeInstanceRequest;
import com.vmware.photon.controller.model.adapterapi.ComputeInstanceRequest.InstanceRequestType;
import com.vmware.photon.controller.model.adapters.util.BaseAdapterContext;
import com.vmware.photon.controller.model.resources.ComputeService.ComputeState;
import com.vmware.photon.controller.model.resources.DiskService.DiskState;
import com.vmware.photon.controller.model.resources.ImageService.ImageState;
import com.vmware.photon.controller.model.resources.NetworkInterfaceService.NetworkInterfaceStateWithDescription;
import com.vmware.photon.controller.model.resources.NetworkService.NetworkState;
import com.vmware.photon.controller.model.resources.ResourceGroupService.ResourceGroupState;
import com.vmware.photon.controller.model.resources.SecurityGroupService.SecurityGroupState;
import com.vmware.photon.controller.model.resources.SubnetService.SubnetState;
import com.vmware.photon.controller.model.resources.TagService.TagState;
import com.vmware.xenon.common.DeferredResult;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.UriUtils;

/**
 * Base context class for adapters that handle {@link ComputeInstanceRequest} request. It
 * {@link #populateContext() loads} NIC related states:
 * 
    *
  • {@link NetworkInterfaceStateWithDescription nicStateWithDesc}
  • *
  • {@link SubnetState subnetState}
  • *
  • {@link NetworkState networkState}
  • *
  • network {@link ResourceGroupState resourceGroupState}
  • *
  • map of {@link SecurityGroupState securityGroupStates}
  • *
* in addition to the states loaded by parent {@link BaseAdapterContext}. */ public class BaseComputeInstanceContext, S extends BaseComputeInstanceContext.BaseNicContext> extends BaseAdapterContext { /** * The class encapsulates NIC related states (resolved by links related to the ComputeState) * used during compute provisioning. */ public static class BaseNicContext { /** * Resolved from {@code ComputeState.networkInterfaceLinks[i]}. */ public NetworkInterfaceStateWithDescription nicStateWithDesc; /** * Resolved from {@code NetworkInterfaceStateWithDescription.subnetLink}. */ public SubnetState subnetState; /** * Resolved from {@code SubnetState.networkLink}. */ public NetworkState networkState; /** * Resolved from FIRST {@code NetworkState.groupLinks}. */ public ResourceGroupState networkRGState; /** * Resolved from {@code NetworkInterfaceStateWithDescription.firewallLinks}. */ public List securityGroupStates = new ArrayList<>(); } /** * Holds allocation data for all VM NICs. */ public final List nics = new ArrayList<>(); /** * The {@link ComputeInstanceRequest request} that is being processed. */ public final ComputeInstanceRequest computeRequest; /** * Where the machine will be placed */ public String placement; /** * A set of TagStates, which are used to tag this instance with. The instance will be tagged in * AWS, Azure and anywhere else if needed. */ public Set tagStates = new HashSet<>(); /** * Supplier/Factory for creating context specific {@link BaseNicContext} instances. */ protected final Supplier nicContextSupplier; public BaseComputeInstanceContext(StatelessService service, ComputeInstanceRequest computeRequest, Supplier nicContextSupplier) { super(service, computeRequest); this.computeRequest = computeRequest; this.nicContextSupplier = nicContextSupplier; } @Override protected URI getParentAuthRef(T context) { if (context.computeRequest.requestType == InstanceRequestType.VALIDATE_CREDENTIALS) { return createInventoryUri( context.service.getHost(), context.computeRequest.authCredentialsLink); } return super.getParentAuthRef(context); } /** * The NIC with lowest deviceId is considered primary. * * @return null if there are no NICs */ public S getPrimaryNic() { return this.nics.stream() .sorted((n1, n2) -> Integer.compare( n1.nicStateWithDesc.deviceIndex, n2.nicStateWithDesc.deviceIndex)) .findFirst().orElse(null); } /** * Populate this context. *

* Notes: *

    *
  • It does NOT call parent * {@link #populateBaseContext(com.vmware.photon.controller.model.adapters.util.BaseAdapterContext.BaseAdapterStage)}
  • *
  • Override {@link #customizeContext(BaseComputeInstanceContext)} if you need to extend * populate logic provided by this method and customize the context. The method follows * Open-Close principle.
  • *
*/ public final DeferredResult populateContext() { return DeferredResult.completed(self()) .thenCompose(this::getPlacement) .thenCompose(this::getNicStates) .thenApply(log("getNicStates")) .thenCompose(this::getNicSubnetStates) .thenApply(log("getNicSubnetStates")) .thenCompose(this::getNicNetworkStates) .thenApply(log("getNicNetworkStates")) .thenCompose(this::getNicSecurityGroupStates) .thenApply(log("getNicSecurityGroupStates")) .thenCompose(this::getTagStates) .thenApply(log("getTagStates")) .thenCompose(this::customizeContext) .thenApply(log("customizeContext")); } private DeferredResult getPlacement(T context) { return Optional.ofNullable(context.child.customProperties) .map(p -> p.get(PLACEMENT_LINK)) .map(link -> { Operation getComputeState = Operation.createGet(service, link); return context.service .sendWithDeferredResult(getComputeState, ComputeState.class) .thenApply(cs -> cs.id); }).orElse(DeferredResult.completed(null)).thenApply(pl -> { context.placement = pl; return context; }); } /** * Hook that might be implemented by descendants to extend {@link #populateContext() populate * logic} and customize the context. */ protected DeferredResult customizeContext(T context) { return DeferredResult.completed(context); } /** * Get NIC states assigned to the compute state we are provisioning. */ private DeferredResult getNicStates(T context) { if (context.child.networkInterfaceLinks == null || context.child.networkInterfaceLinks.isEmpty()) { return DeferredResult.completed(context); } List> getStatesDR = context.child.networkInterfaceLinks.stream() .map(nicStateLink -> { S nicContext = context.nicContextSupplier.get(); context.nics.add(nicContext); URI nicStateUri = NetworkInterfaceStateWithDescription .buildUri(UriUtils.buildUri(context.service.getHost(), nicStateLink)); Operation op = Operation.createGet(nicStateUri); return context.service .sendWithDeferredResult(op, NetworkInterfaceStateWithDescription.class) .thenAccept(nsWithDesc -> nicContext.nicStateWithDesc = nsWithDesc); }) .collect(Collectors.toList()); return DeferredResult.allOf(getStatesDR).handle((all, exc) -> { if (exc != null) { String msg = String.format( "Error getting NIC states for [%s] VM.", context.child.name); throw new IllegalStateException(msg, exc); } return context; }); } /** * Get {@link SubnetState}s the NICs are assigned to. */ private DeferredResult getNicSubnetStates(T context) { if (context.nics.isEmpty()) { return DeferredResult.completed(context); } List> getStatesDR = context.nics.stream() .map(nicContext -> { Operation op = Operation.createGet( context.service.getHost(), nicContext.nicStateWithDesc.subnetLink); return context.service .sendWithDeferredResult(op, SubnetState.class) .thenAccept(subnetState -> nicContext.subnetState = subnetState); }) .collect(Collectors.toList()); return DeferredResult.allOf(getStatesDR).handle((all, exc) -> { if (exc != null) { String msg = String.format( "Error getting NIC Subnet states for [%s] VM.", context.child.name); throw new IllegalStateException(msg, exc); } return context; }); } /** * Get {@link NetworkState}s containing the {@link SubnetState}s the NICs are assigned to. * * @see #getNicSubnetStates(BaseComputeInstanceContext) */ private DeferredResult getNicNetworkStates(T context) { if (context.nics.isEmpty()) { return DeferredResult.completed(context); } List> getStatesDR = context.nics.stream() .map(nicContext -> { Operation op = Operation.createGet( context.service.getHost(), nicContext.subnetState.networkLink); return context.service .sendWithDeferredResult(op, NetworkState.class) .thenAccept(networkState -> nicContext.networkState = networkState); }) .collect(Collectors.toList()); return DeferredResult.allOf(getStatesDR).handle((all, exc) -> { if (exc != null) { String msg = String.format( "Error getting NIC Network states for [%s] VM.", context.child.name); throw new IllegalStateException(msg, exc); } return context; }); } /** * Get {@link SecurityGroupState}s assigned to NICs. */ private DeferredResult getNicSecurityGroupStates(T context) { if (context.nics.isEmpty()) { return DeferredResult.completed(context); } List> getSecurityGroupDR = context.nics.stream() .filter(nicContext -> // Only those that have at least 1 security group. nicContext.nicStateWithDesc.securityGroupLinks != null && !nicContext.nicStateWithDesc.securityGroupLinks.isEmpty()) .flatMap(nicContext -> nicContext.nicStateWithDesc.securityGroupLinks.stream() .map(securityGroupLink -> { Operation op = Operation.createGet( context.service.getHost(), securityGroupLink); return context.service .sendWithDeferredResult(op, SecurityGroupState.class) .thenAccept(securityGroupState -> nicContext.securityGroupStates .add(securityGroupState)); })) .collect(Collectors.toList()); return DeferredResult.allOf(getSecurityGroupDR).handle((all, exc) -> { if (exc != null) { String msg = String.format( "Error getting NIC SecurityGroup states for [%s] VM.", context.child.name); throw new IllegalStateException(msg, exc); } return context; }); } /** * Populate all child.tagLinks as {@link TagState} objects into {@link #tagStates} * field. * * @param context * The context object to populate TagStates into. * @return The same context object with populated TagStates. */ private DeferredResult getTagStates(T context) { if (context.child.tagLinks == null || context.child.tagLinks.isEmpty()) { return DeferredResult.completed(context); } List> collectTagsDRs = context.child.tagLinks.stream() .map(tagLink -> Operation.createGet(context.service, tagLink)) .map(operation -> context.service.sendWithDeferredResult(operation, TagState.class)) .collect(Collectors.toList()); return DeferredResult.allOf(collectTagsDRs).handle((collectedTagStates, ex) -> { if (ex != null) { String msg = String.format( "Error getting TagState states for [%s] VM.", context.child.name); throw new IllegalStateException(msg, ex); } context.tagStates.addAll(collectedTagStates); return context; }); } /** * Helper method to load {@link ImageSource} either from {@code ImageState} that is pointed by * {@code bootDisk.imageLink} or directly from {@code bootDisk.sourceImageReference}. */ public DeferredResult getImageSource(DiskState bootDisk) { if (bootDisk == null) { return DeferredResult.failed(new IllegalStateException("bootDisk should be specified")); } if (bootDisk.sourceImageReference == null && bootDisk.imageLink == null) { return DeferredResult.failed(new IllegalStateException( "Either bootDisk.sourceImageReference or bootDisk.imageLink should be specified")); } final DeferredResult imageSourceDR; if (bootDisk.imageLink != null) { // Either get ImageState as pointed by 'bootDisk.imageLink' Operation getImageStateOp = Operation.createGet( this.service.getHost(), bootDisk.imageLink); imageSourceDR = this.service .sendWithDeferredResult(getImageStateOp, ImageState.class) .thenApply(ImageSource::fromImageState); } else { // Or use directly 'bootDisk.sourceImageReference' as 'image native id' imageSourceDR = DeferredResult.completed( ImageSource.fromRef(bootDisk.sourceImageReference.toString())); } return imageSourceDR; } /** * Generic enough representation of a source of an image, such as local ImageState or native/raw * image reference. */ public static class ImageSource { /** * The type of the source so we can do/apply conditional logic. */ public enum Type { PUBLIC_IMAGE, PRIVATE_IMAGE, IMAGE_REFERENCE; } /** * Create image source from actual public/private ImageState. */ public static ImageSource fromImageState(ImageState imageState) { final ImageSource imageHolder = new ImageSource(); if (imageState.isPublicImage()) { imageHolder.type = Type.PUBLIC_IMAGE; } else if (imageState.isPrivateImage()) { imageHolder.type = Type.PRIVATE_IMAGE; } else { throw new IllegalStateException("Unexpected ImageState type."); } imageHolder.source = imageState; return imageHolder; } /** * Create image source from native/raw image reference. */ public static ImageSource fromRef(String imageRef) { return new ImageSource(Type.IMAGE_REFERENCE, imageRef); } public Type type; public Object source; public ImageSource(Type type, Object source) { this.type = type; this.source = source; } private ImageSource() { } /** * Cast image source to actual ImageState. */ public ImageState asImageState() { return this.source instanceof ImageState ? (ImageState) this.source : null; } /** * Cast image source to actual image reference. */ public String asRef() { return this.source instanceof String ? (String) this.source : null; } /** * Helper method converting supported image sources to native image id. */ public String asNativeId() { if (asImageState() != null) { return asImageState().id; } if (asRef() != null) { return asRef(); } return null; } } }