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

com.vmware.photon.controller.model.adapters.azure.enumeration.AzureImageEnumerationAdapterService Maven / Gradle / Ivy

/*
 * 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.azure.enumeration;

import static java.util.Collections.singletonList;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.azure.CloudException;
import com.microsoft.azure.management.compute.ImageDataDisk;
import com.microsoft.azure.management.compute.ImageOSDisk;
import com.microsoft.azure.management.compute.PurchasePlan;
import com.microsoft.azure.management.compute.VirtualMachineCustomImage;
import com.microsoft.azure.management.compute.implementation.VirtualMachineImageInner;
import com.microsoft.azure.management.compute.implementation.VirtualMachineImageResourceInner;
import com.microsoft.azure.management.compute.implementation.VirtualMachineImagesInner;

import org.apache.commons.lang3.StringUtils;

import com.vmware.photon.controller.model.adapterapi.ImageEnumerateRequest;
import com.vmware.photon.controller.model.adapterapi.ImageEnumerateRequest.ImageEnumerateRequestType;
import com.vmware.photon.controller.model.adapters.azure.AzureUriPaths;
import com.vmware.photon.controller.model.adapters.azure.constants.AzureConstants;
import com.vmware.photon.controller.model.adapters.azure.utils.AzureSdkClients;
import com.vmware.photon.controller.model.adapters.util.AdapterUtils;
import com.vmware.photon.controller.model.adapters.util.TaskManager;
import com.vmware.photon.controller.model.adapters.util.enums.EndpointEnumerationProcess;
import com.vmware.photon.controller.model.resources.ImageService;
import com.vmware.photon.controller.model.resources.ImageService.ImageState;
import com.vmware.photon.controller.model.tasks.ImageEnumerationTaskService.ImageEnumerationTaskState;
import com.vmware.xenon.common.DeferredResult;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.services.common.QueryTask.Query.Builder;

/**
 * Azure image enumeration adapter responsible to enumerate Azure {@link ImageState}s. It handles
 * {@link ImageEnumerateRequest} as send/initiated by {@code ImageEnumerationTaskService}.
 */
public class AzureImageEnumerationAdapterService extends StatelessService {

    public static final String SELF_LINK = AzureUriPaths.AZURE_IMAGE_ENUMERATION_ADAPTER;

    public static final String DEFAUL_IMAGES_SOURCE_PROPERTY = "photon-model.adapter.azure.images.default.source";

    /**
     * Public JSON file of default Azure images.
     */
    public static final String DEFAUL_IMAGES_SOURCE_VALUE = "https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/arm-compute/quickstart-templates/aliases.json";

    public static String getDefaultImagesSource() {
        return System.getProperty(DEFAUL_IMAGES_SOURCE_PROPERTY, DEFAUL_IMAGES_SOURCE_VALUE);
    }

    public static final String IMAGES_LOAD_MODE_PROPERTY = "photon-model.adapter.azure.images.load.mode";

    public static enum ImagesLoadMode {
        /**
         * Load standard images (in case of public and private images enumeration).
         */
        STANDARD,

        /**
         * Load default images (in case of public images enumeration).
         */
        DEFAULT,

        /**
         * Load standard and default images.
         */
        ALL;
    }

    /**
     * Get {@code ImagesLoadMode} from {@value #IMAGES_LOAD_MODE_PROPERTY} system property.
     *
     * @return by default return {@link ImagesLoadMode#ALL}
     */
    public static ImagesLoadMode getImagesLoadMode() {

        String imagesLoadMode = System.getProperty(
                IMAGES_LOAD_MODE_PROPERTY,
                ImagesLoadMode.ALL.name());

        try {
            return ImagesLoadMode.valueOf(imagesLoadMode);

        } catch (Exception exc) {

            return ImagesLoadMode.ALL;
        }
    }

    public static final String IMAGES_PAGE_SIZE_PROPERTY = "photon-model.adapter.azure.images.page.size";

    /**
     * Get images page size from {@value #IMAGES_PAGE_SIZE_PROPERTY} system property.
     *
     * @return by default return 100
     */
    public static int getImagesPageSize() {

        final int DEFAULT_IMAGES_PAGE_SIZE = 100;

        String imagesPageSizeStr = System.getProperty(
                IMAGES_PAGE_SIZE_PROPERTY,
                String.valueOf(DEFAULT_IMAGES_PAGE_SIZE));

        try {
            return Integer.parseInt(imagesPageSizeStr);

        } catch (NumberFormatException exc) {

            return DEFAULT_IMAGES_PAGE_SIZE;
        }
    }

    /**
     * {@link EndpointEnumerationProcess} specialization that loads Azure {@link AzureImageData}s
     * into {@link ImageState} store.
     */
    private static class AzureImageEnumerationContext extends
            EndpointEnumerationProcess {

        static final String DEFAULT_FILTER_VALUE = "default";

        static final VirtualMachineImageResourceInner[] DEFAULT_IMAGES_FILTER = toImageFilter(
                new String[] { DEFAULT_FILTER_VALUE,
                        DEFAULT_FILTER_VALUE,
                        DEFAULT_FILTER_VALUE,
                        DEFAULT_FILTER_VALUE });

        static final String NEXT_PAGE_LINK = "azureImages_";

        /**
         * The underlying image-enum request.
         */
        final ImageEnumerateRequest request;

        /**
         * The image-enum task that triggered this request.
         */
        ImageEnumerationTaskState imageEnumTaskState;

        /**
         * The canonized image filter (of imageEnumTaskState.filter) in the form of
         * 'publisher:offer:sku:version'. Special cases:
         * 
    *
  • empty VirtualMachineImageResource.name means 'no filter' at specific position
  • *
  • 'default' string filter is mapped to DEFAULT_IMAGES_FILTER and means load default * images ONLY
  • *
*/ VirtualMachineImageResourceInner[] imageFilter; AzureSdkClients azureClient; AzureImagesLoader azureImagesLoader; TaskManager taskManager; AzureImageEnumerationContext( AzureImageEnumerationAdapterService service, ImageEnumerateRequest request) { super(service, request.resourceReference, ImageState.class, ImageService.FACTORY_LINK); this.taskManager = new TaskManager(this.service, request.taskReference, request.resourceLink()); this.request = request; if (request.requestType == ImageEnumerateRequestType.PUBLIC) { // Public/Shared images should NOT consider tenantLinks and endpointLink setApplyInfraFields(false); } } ImagesLoadMode perRequestImagesLoadMode() { return this.imageFilter == DEFAULT_IMAGES_FILTER ? ImagesLoadMode.DEFAULT : getImagesLoadMode(); } /** *
    *
  • Extract calling image-enum task state prior end-point loading.
  • *
  • Extract end-point region id once end-point state is loaded.
  • *
*/ @Override protected DeferredResult getEndpointState( AzureImageEnumerationContext context) { return DeferredResult.completed(context) .thenCompose(this::getImageEnumTaskState) .thenCompose(ctx -> super.getEndpointState(ctx)); } /** * Extract {@link ImageEnumerationTaskState} from {@code request.taskReference} and set it * to {@link #imageEnumTaskState}. */ private DeferredResult getImageEnumTaskState( AzureImageEnumerationContext context) { Operation op = Operation.createGet(context.request.taskReference); return context.service .sendWithDeferredResult(op, ImageEnumerationTaskState.class) .thenApply(state -> { context.imageEnumTaskState = state; context.imageFilter = createImageFilter(context.imageEnumTaskState.filter); return context; }); } /** * Create Azure client prior core page-by-page enumeration. */ @Override protected DeferredResult enumeratePageByPage( AzureImageEnumerationContext context) { return DeferredResult.completed(context) .thenApply(this::createAzureClient) .thenCompose(ctx -> super.enumeratePageByPage(ctx)); } protected AzureImageEnumerationContext createAzureClient( AzureImageEnumerationContext context) { context.azureClient = new AzureSdkClients( ((AzureImageEnumerationAdapterService) context.service).executorService, context.endpointAuthState); return context; } /** * Get next page of Azure images from AzureImagesLoader. */ @Override protected DeferredResult getExternalResources(String nextPageLink) { return getAzureImagesPage(getAzureImagesLoader()); } /** * Initialize correct images loader depending on {@code ImageEnumerateRequestType} and * {@code ImagesLoadMode}. */ private AzureImagesLoader getAzureImagesLoader() { if (this.azureImagesLoader == null) { // Initialize correct images loader... ImagesLoadMode imagesLoadMode = perRequestImagesLoadMode(); if (this.request.requestType == ImageEnumerateRequestType.PUBLIC) { // PUBLIC images enum if (imagesLoadMode == ImagesLoadMode.DEFAULT) { this.azureImagesLoader = new DefaultImagesLoader(this); } else if (imagesLoadMode == ImagesLoadMode.STANDARD) { this.azureImagesLoader = new StandardImagesLoader( this, getImagesPageSize()); } else if (imagesLoadMode == ImagesLoadMode.ALL) { this.azureImagesLoader = new ConcatenatedAzureImagesLoader(this, Arrays.asList( new DefaultImagesLoader(this), new StandardImagesLoader(this, getImagesPageSize()))); } } else { // PRIVATE images enumeration this.azureImagesLoader = new PrivateImagesLoader(this, getImagesPageSize()); } } return this.azureImagesLoader; } /** * Read Azure images page-by-page as served by passed {@link AzureImagesLoader} and convert * to {@link RemoteResourcesPage}. */ private DeferredResult getAzureImagesPage( AzureImagesLoader azureImagesLoader) { final RemoteResourcesPage page = new RemoteResourcesPage(); if (azureImagesLoader.hasNext()) { // Consume this page from underlying Iterator for (AzureImageData image : azureImagesLoader.next()) { page.resourcesPage.put(image.id, image); } } if (azureImagesLoader.hasNext()) { // Return a non-null nextPageLink to the parent so we are called back. page.nextPageLink = NEXT_PAGE_LINK + azureImagesLoader.getClass().getSimpleName() + "_" + azureImagesLoader.pageNumber; } return DeferredResult.completed(page); } @Override protected DeferredResult buildLocalResourceState( AzureImageData azureImageData, ImageState existingImageState) { LocalStateHolder holder = new LocalStateHolder(); holder.localState = new ImageState(); if (existingImageState == null) { // Create flow if (this.request.requestType == ImageEnumerateRequestType.PUBLIC) { holder.localState.endpointType = this.endpointState.endpointType; } } else { // Update flow: do nothing } // Both flows - populate from remote Image holder.localState.name = azureImageData.name; holder.localState.description = azureImageData.description; holder.localState.osFamily = azureImageData.osFamily; holder.localState.diskConfigs = azureImageData.diskConfigs; holder.remoteTags.putAll(azureImageData.tags); return DeferredResult.completed(holder); } /** *
    *
  • During PUBLIC image enum explicitly set {@code endpointType}.
  • *
  • During PRIVATE image enum setting of {@code tenantLinks} and {@code endpointLink} (by * default logic) is enough.
  • *
  • During BOTH image enum explicitly set {@code regionId}.
  • *
*/ @Override protected void customizeLocalStatesQuery(Builder qBuilder) { if (this.request.requestType == ImageEnumerateRequestType.PUBLIC) { qBuilder.addFieldClause( ImageState.FIELD_NAME_ENDPOINT_TYPE, this.endpointState.endpointType); } } static VirtualMachineImageResourceInner[] createImageFilter(String filter) { if (DEFAULT_FILTER_VALUE.equalsIgnoreCase(filter)) { return DEFAULT_IMAGES_FILTER; } // publisher:offer:sku:version String[] strFilters = { "", "", "", "" }; if (filter != null && !filter.isEmpty()) { String[] tokens = StringUtils.splitPreserveAllTokens(filter, ":"); if (tokens.length == strFilters.length) { strFilters = tokens; } else { return DEFAULT_IMAGES_FILTER; } } return toImageFilter(strFilters); } static VirtualMachineImageResourceInner[] toImageFilter(String[] strFilters) { VirtualMachineImageResourceInner[] posv = new VirtualMachineImageResourceInner[strFilters.length]; for (int i = 0; i < strFilters.length; i++) { // Create dummy VirtualMachineImageResource with just name being set. posv[i] = new VirtualMachineImageResourceInner(); posv[i].withName(strFilters[i]); } return posv; } static String toImageReference(String publisher, String offer, String sku, String version) { return publisher + ":" + offer + ":" + sku + ":" + version; } } private ExecutorService executorService; public AzureImageEnumerationAdapterService() { super.toggleOption(ServiceOption.INSTRUMENTATION, true); } @Override public void handleStart(Operation startPost) { this.executorService = getHost().allocateExecutor(this); super.handleStart(startPost); } @Override public void handleStop(Operation delete) { this.executorService.shutdown(); AdapterUtils.awaitTermination(this.executorService); super.handleStop(delete); } @Override public void handlePatch(Operation op) { if (!op.hasBody()) { op.fail(new IllegalArgumentException("body is required")); return; } // Immediately complete the Operation from calling task. op.complete(); AzureImageEnumerationContext ctx = new AzureImageEnumerationContext( this, op.getBody(ImageEnumerateRequest.class)); if (ctx.request.isMockRequest) { // Complete the task with FINISHED completeWithSuccess(ctx); return; } final String msg = ctx.request.requestType + " images enumeration"; logFine(() -> msg + ": STARTED"); // Start image enumeration process... ctx.enumerate() .whenComplete((o, e) -> { // Once done patch the calling task with correct stage. if (e == null) { logFine(() -> msg + ": COMPLETED"); completeWithSuccess(ctx); } else { logSevere(() -> msg + ": FAILED with " + Utils.toString(e)); completeWithFailure(ctx, e); } }); } private void completeWithFailure(AzureImageEnumerationContext ctx, Throwable exc) { ctx.taskManager.patchTaskToFailure(exc); if (ctx.azureClient != null) { ctx.azureClient.close(); } } private void completeWithSuccess(AzureImageEnumerationContext ctx) { // Report the success back to the caller ctx.taskManager.finishTask(); if (ctx.azureClient != null) { ctx.azureClient.close(); } } /** * An Azure default images loader that reads pre-defined images from a file and exposes * them as {@code VirtualMachineImage}s. * *
     * {
          "$schema":"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json",
          "contentVersion":"1.0.0.0",
          "parameters":{},
          "variables":{},
          "resources":[],

          "outputs":{
            "aliases":{
              "type":"object",
              "value":{

                "Linux":{
                  "CentOS":{
                    "publisher":"OpenLogic",
                    "offer":"CentOS",
                    "sku":"7.3",
                    "version":"latest"
                  },
                  "CoreOS":{
                    "publisher":"CoreOS",
                    "offer":"CoreOS",
                    "sku":"Stable",
                    "version":"latest"
                  },
                  "Debian":{
                    "publisher":"credativ",
                    "offer":"Debian",
                    "sku":"8",
                    "version":"latest"
                  },
                  "openSUSE-Leap": {
                    "publisher":"SUSE",
                    "offer":"openSUSE-Leap",
                    "sku":"42.2",
                    "version": "latest"
                  },
                  "RHEL":{
                    "publisher":"RedHat",
                    "offer":"RHEL",
                    "sku":"7.3",
                    "version":"latest"
                  },
                  "SLES":{
                    "publisher":"SUSE",
                    "offer":"SLES",
                    "sku":"12-SP2",
                    "version":"latest"
                  },
                  "UbuntuLTS":{
                    "publisher":"Canonical",
                    "offer":"UbuntuServer",
                    "sku":"16.04-LTS",
                    "version":"latest"
                  }
                },

                "Windows":{
                  "Win2016Datacenter":{
                    "publisher":"MicrosoftWindowsServer",
                    "offer":"WindowsServer",
                    "sku":"2016-Datacenter",
                    "version":"latest"
                  },
                  "Win2012R2Datacenter":{
                    "publisher":"MicrosoftWindowsServer",
                    "offer":"WindowsServer",
                    "sku":"2012-R2-Datacenter",
                    "version":"latest"
                  },
                  "Win2012Datacenter":{
                    "publisher":"MicrosoftWindowsServer",
                    "offer":"WindowsServer",
                    "sku":"2012-Datacenter",
                    "version":"latest"
                  },
                  "Win2008R2SP1":{
                    "publisher":"MicrosoftWindowsServer",
                    "offer":"WindowsServer",
                    "sku":"2008-R2-SP1",
                    "version":"latest"
                  }
                }
              }
            }
          }
        }
     * 
*/ private static class DefaultImagesLoader extends AzureImagesLoader { /** * Represents the inner most JSON node in the JSON file. */ private static class ImageRef { String osFamily; String publisher; String offer; String sku; String version; @Override public String toString() { return getClass().getSimpleName() + " [osFamily=" + this.osFamily + ", publisher=" + this.publisher + ", offer=" + this.offer + ", sku=" + this.sku + ", version=" + this.version + "]"; } } DefaultImagesLoader(AzureImageEnumerationContext ctx) { super(ctx); } @Override public String toString() { return "Enumerating " + ctx.request.requestType + " default images"; } /** * All default images are returned within a single page, so return {@code true} just * once, prior consuming {@link #next()}. */ @Override public boolean hasNext() { return this.pageNumber == 0; } @Override List nextPage() { return ((CompletableFuture>) load().toCompletionStage()).join(); } /** * Download aliases.json file, parse it and convert image entries presented to * {@code AzureImageData}s. */ private DeferredResult> load() { URI defaultImagesSource = URI.create(getDefaultImagesSource()); return this.ctx.service .sendWithDeferredResult(Operation.createGet(defaultImagesSource), String.class) .thenApply(this::parseJson) .thenApply(this::toAzureImageDatas); } /** * Parse aliases.json file to {@code ImageRef}s. */ private List parseJson(String jsonText) { JsonObject rootJson = Utils.fromJson(jsonText, JsonObject.class); Set> byOsFamily = rootJson .getAsJsonObject("outputs") .getAsJsonObject("aliases") .getAsJsonObject("value") .entrySet(); List imageRefs = new ArrayList<>(); for (Entry byOsFamilyEntry : byOsFamily) { for (Entry byOsEntry : byOsFamilyEntry.getValue() .getAsJsonObject() .entrySet()) { ImageRef imageRef = Utils.fromJson( byOsEntry.getValue().getAsJsonObject(), ImageRef.class); imageRef.osFamily = byOsFamilyEntry.getKey(); imageRefs.add(imageRef); } } return imageRefs; } /** * Convert {@code ImageRef}s to {@code AzureImageData}s. * * @see #toImageState(ImageRef) */ private List toAzureImageDatas(List imageRefs) { return imageRefs.stream().map(this::toAzureImageData).collect(Collectors.toList()); } /** * Convert {@code ImageRef} to {@code AzureImageData}. */ private AzureImageData toAzureImageData(ImageRef imageRef) { final AzureImageData azureImageData = new AzureImageData(); azureImageData.id = AzureImageEnumerationContext.toImageReference( imageRef.publisher, imageRef.offer, imageRef.sku, imageRef.version); azureImageData.name = azureImageData.id; azureImageData.description = azureImageData.id; azureImageData.osFamily = imageRef.osFamily; return azureImageData; } } /** * An Azure images loader that traverses (in depth) 'publisher-offer-sku-version' hierarchy and * exposes {@code ImageState}s through an {@code Iterator} interface. */ private static class StandardImagesLoader extends AzureImagesLoader { final VirtualMachineImagesInner imagesOp; private Iterator publishersIt; private VirtualMachineImageResourceInner currentPublisher; // IMPORTANT: Do not use Collections.emptyIterator! We need brand new instance. private Iterator offersIt = new ArrayList() .iterator(); private VirtualMachineImageResourceInner currentOffer; private Iterator skusIt = new ArrayList() .iterator(); private VirtualMachineImageResourceInner currentSku; private Iterator versionsIt = new ArrayList() .iterator(); private VirtualMachineImageResourceInner currentVersion; final int pageSize; StandardImagesLoader(AzureImageEnumerationContext ctx, int pageSize) { super(ctx); this.pageSize = pageSize; this.imagesOp = ctx.azureClient .getComputeManagementClientImpl() .virtualMachineImages(); } @Override public String toString() { return "Enumerating " + this.ctx.request.requestType + " standard images by [" + Arrays.asList(this.ctx.imageFilter).stream() .map(VirtualMachineImageResourceInner::name) .collect(Collectors.joining(":")) + "]"; } @Override public boolean hasNext() { try { loadPublishers(); } catch (Exception e) { throw new RuntimeException("An error while traversing Azure images.", e); } return hasNext(this.publishersIt); } /** * Checks whether passed iterator or any of its underlying iterators has more elements. */ private boolean hasNext(Iterator it) { if (it == this.publishersIt) { return it.hasNext() || hasNext(this.offersIt); } if (it == this.offersIt) { return it.hasNext() || hasNext(this.skusIt); } if (it == this.skusIt) { return it.hasNext() || hasNext(this.versionsIt); } if (it == this.versionsIt) { return it.hasNext(); } throw new IllegalStateException("unexpected code flow"); } @Override List nextPage() { List page = new ArrayList<>(); try { while (hasNext(this.publishersIt)) { // Check whether underlying iterator is exhausted if (hasNext(this.offersIt)) { // Continue from last stop/exit point: use current 'publisher' } else { // Move to next 'publisher' this.currentPublisher = this.publishersIt.next(); loadOffers(); } while (hasNext(this.offersIt)) { // Check whether underlying iterator is exhausted if (hasNext(this.skusIt)) { // Continue from last stop/exit point: use current 'offer' } else { // Move to next 'offer' this.currentOffer = this.offersIt.next(); loadSkus(); } while (hasNext(this.skusIt)) { // Check whether underlying iterator is exhausted if (hasNext(this.versionsIt)) { // Continue from last stop/exit point: use current 'sku' } else { // Move to next 'sku' this.currentSku = this.skusIt.next(); loadVersions(); } while (hasNext(this.versionsIt)) { this.currentVersion = this.versionsIt.next(); VirtualMachineImageInner azureImage = loadVirtualMachineImage(); AzureImageData azureImageData = toAzureImageData( azureImage); page.add(azureImageData); if (page.size() == this.pageSize) { return page; } } } } } } catch (Exception e) { throw new RuntimeException("An error while traversing Azure images.", e); } return page; } /** * Load {@code #publishersIt}. */ private void loadPublishers() throws CloudException, IllegalArgumentException, IOException { if (this.publishersIt != null) { // already loaded return; } VirtualMachineImageResourceInner publisherFilter = this.ctx.imageFilter[0]; if (publisherFilter.name().isEmpty()) { // Get ALL publishers List publishers = this.imagesOp .listPublishers(this.ctx.getEndpointRegion()); this.publishersIt = publishers.iterator(); this.ctx.service.logFine(() -> "publishers = " + publishers.size()); } else { // Filter publishers this.publishersIt = singletonList(publisherFilter).iterator(); this.ctx.service .logFine(() -> "publisher-filter = " + publisherFilter.name()); } } /** * Load {@code #offersIt}. */ private void loadOffers() throws CloudException, IOException { VirtualMachineImageResourceInner offerFilter = this.ctx.imageFilter[1]; if (offerFilter.name().isEmpty()) { // Get ALL offers List offers = this.imagesOp.listOffers( this.ctx.getEndpointRegion(), this.currentPublisher.name()); this.offersIt = offers.iterator(); this.ctx.service.logFine( () -> "offers (per " + this.currentPublisher.name() + ") = " + offers.size()); } else { // Filter offers this.offersIt = singletonList(offerFilter).iterator(); this.ctx.service.logFine( () -> "offer-filter (per " + this.currentPublisher.name() + ") = " + offerFilter.name()); } } /** * Load {@code #skusIt}. */ private void loadSkus() throws CloudException, IOException { VirtualMachineImageResourceInner skuFilter = this.ctx.imageFilter[2]; if (skuFilter.name().isEmpty()) { // Get ALL skus List skus = this.imagesOp.listSkus( this.ctx.getEndpointRegion(), this.currentPublisher.name(), this.currentOffer.name()); this.skusIt = skus.iterator(); this.ctx.service.logFine( () -> "skus (per " + this.currentPublisher.name() + "/" + this.currentOffer.name() + ") = " + skus.size()); } else { // Filter sku this.skusIt = singletonList(skuFilter).iterator(); this.ctx.service.logFine( () -> "sku-filter (per " + this.currentPublisher.name() + "/" + this.currentOffer.name() + ") = " + skuFilter.name()); } } private void loadVersions() throws CloudException, IOException { VirtualMachineImageResourceInner versionFilter = this.ctx.imageFilter[3]; if (versionFilter.name().isEmpty()) { // Get ALL versions List versions = this.imagesOp.list( this.ctx.getEndpointRegion(), this.currentPublisher.name(), this.currentOffer.name(), this.currentSku.name(), null, null, null); this.versionsIt = versions.iterator(); this.ctx.service.logFine(() -> "versions (per " + this.currentPublisher.name() + "/" + this.currentOffer.name() + "/" + this.currentSku.name() + ") = " + versions.size()); } else { // Filter versions this.versionsIt = singletonList(versionFilter).iterator(); this.ctx.service.logFine( () -> "version-filter (per " + this.currentPublisher.name() + "/" + this.currentOffer.name() + "/" + this.currentSku.name() + ") = " + versionFilter.name()); } } private VirtualMachineImageInner loadVirtualMachineImage() throws CloudException, IOException { VirtualMachineImageInner image = this.imagesOp.get( this.ctx.getEndpointRegion(), this.currentPublisher.name(), this.currentOffer.name(), this.currentSku.name(), this.currentVersion.name()); if (image.plan() == null) { // For some reason some images does not have Plan so we create one PurchasePlan plan = new PurchasePlan(); plan.withPublisher(this.currentPublisher.name()); plan.withProduct(this.currentOffer.name()); plan.withName(this.currentSku.name()); image.withPlan(plan); } return image; } /** * Convert {@code VirtualMachineImageInner} to {@code AzureImageData}. */ private AzureImageData toAzureImageData(VirtualMachineImageInner azureImage) { final AzureImageData azureImageData = new AzureImageData(); azureImageData.id = AzureImageEnumerationContext.toImageReference( azureImage.plan().publisher(), azureImage.plan().product(), azureImage.plan().name(), azureImage.name()); azureImageData.name = azureImageData.id; azureImageData.description = azureImageData.id; if (azureImage.osDiskImage() != null && azureImage.osDiskImage().operatingSystem() != null) { azureImageData.osFamily = azureImage.osDiskImage().operatingSystem().name(); } if (azureImage.tags() != null) { azureImageData.tags.putAll(azureImage.tags()); } return azureImageData; } } /** * An Azure private/custom images loader that traverses custom images under current subscription * and exposes {@code VirtualMachineImage}s through an {@code Iterator} interface. */ private static class PrivateImagesLoader extends AzureImagesLoader { final int pageSize; // Ref to underlying Azure paged images final Iterator azurePagedImagesIt; PrivateImagesLoader(AzureImageEnumerationContext ctx, int pageSize) { super(ctx); this.pageSize = pageSize; this.azurePagedImagesIt = ctx.azureClient .getComputeManager() .virtualMachineCustomImages() .list() .iterator(); } @Override public String toString() { return "Enumerating " + this.ctx.request.requestType + " images"; } @Override public boolean hasNext() { return this.azurePagedImagesIt.hasNext(); } @Override List nextPage() { List page = new ArrayList<>(); for (int idx = 0; idx < this.pageSize && this.azurePagedImagesIt.hasNext(); idx++) { VirtualMachineCustomImage azureImage = this.azurePagedImagesIt.next(); AzureImageData azureImageData = toAzureImageData(azureImage); page.add(azureImageData); } return page; } private AzureImageData toAzureImageData(VirtualMachineCustomImage azureCustomImage) { final AzureImageData azureImageData = new AzureImageData(); azureImageData.id = azureCustomImage.id(); azureImageData.name = azureCustomImage.name(); azureImageData.description = azureImageData.name; // Configure Disks azureImageData.diskConfigs = new ArrayList<>(); // Configure OS Disk final ImageOSDisk azureOsDiskImage = azureCustomImage.osDiskImage(); if (azureOsDiskImage != null) { if (azureOsDiskImage.osType() != null) { azureImageData.osFamily = azureOsDiskImage.osType().name(); } ImageState.DiskConfiguration osDiskConfig = new ImageState.DiskConfiguration(); osDiskConfig.properties = new HashMap<>(); if (azureOsDiskImage.diskSizeGB() != null) { osDiskConfig.capacityMBytes = azureOsDiskImage.diskSizeGB() * 1024; } if (azureOsDiskImage.caching() != null) { osDiskConfig.properties.put( AzureConstants.AZURE_OSDISK_CACHING, azureOsDiskImage.caching().name()); } if (azureOsDiskImage.blobUri() != null) { osDiskConfig.properties.put( AzureConstants.AZURE_OSDISK_BLOB_URI, azureOsDiskImage.blobUri()); } azureImageData.diskConfigs.add(osDiskConfig); } // Configure Data Disk for (ImageDataDisk azureDataDisk : azureCustomImage.dataDiskImages().values()) { ImageState.DiskConfiguration dataDiskConfig = new ImageState.DiskConfiguration(); dataDiskConfig.properties = new HashMap<>(); dataDiskConfig.properties.put( AzureConstants.AZURE_DISK_LUN, Integer.toString(azureDataDisk.lun())); if (azureDataDisk.diskSizeGB() != null) { dataDiskConfig.capacityMBytes = azureDataDisk.diskSizeGB() * 1024; } if (azureDataDisk.caching() != null) { dataDiskConfig.properties.put( AzureConstants.AZURE_DISK_CACHING, azureDataDisk.caching().name()); } if (azureDataDisk.blobUri() != null) { dataDiskConfig.properties.put( AzureConstants.AZURE_DISK_BLOB_URI, azureDataDisk.blobUri()); } azureImageData.diskConfigs.add(dataDiskConfig); } // Configure tags if (azureCustomImage.tags() != null) { azureImageData.tags.putAll(azureCustomImage.tags()); } return azureImageData; } } /** * A generic abstraction of Azure image, such as standard (VirtualMachineImageInner), default * (ImageRef) and private (VirtualMachineCustomImage). * * @see StandardImagesLoader#toAzureImageData(VirtualMachineImageInner) * @see DefaultImagesLoader#toAzureImageData(DefaultImagesLoader.ImageRef) * @see PrivateImagesLoader#toAzureImageData(VirtualMachineCustomImage) */ // NOTE: reuse ImageState in order not to duplicate all existing props. private static class AzureImageData extends ImageState { Map tags = new HashMap<>(); } /** * Defines the contract of a loader capable to load Azure images, such as standard, default and * private. */ private abstract static class AzureImagesLoader implements Iterator> { final AzureImageEnumerationContext ctx; /** * Return the number of pages returned by {@link #next()} so far. */ int pageNumber = 0; /** * Return the total number of elements returned by {@link #next()} so far. */ int totalNumber = 0; AzureImagesLoader(AzureImageEnumerationContext ctx) { this.ctx = ctx; } /** * Provides common functionality (such as 1) hasNext validation, 2) pageNumber and * totalNumber manipulation and 3) logging) so descendants should focus only on providing * {@link #nextPage() next page}. */ @Override public final List next() { if (!hasNext()) { throw new NoSuchElementException( getClass().getSimpleName() + " has already been consumed."); } if (this.pageNumber == 0) { this.ctx.service.logFine(() -> toString() + ": STARTING"); } List page = nextPage(); this.pageNumber++; this.totalNumber += page.size(); if (!hasNext()) { this.ctx.service.logFine( () -> toString() + ": TOTAL number = " + this.totalNumber); } return page; } /** * Descendants should focus on just loading next page. */ abstract List nextPage(); /** * Used for logging purposes, so 'enforce' descendants to provide user-friendly message. */ @Override public abstract String toString(); } /** * Combines multiple {@link AzureImagesLoader} into a single {@code AzureImagesLoader}. The * returned loader iterates across the elements of each loader and the loaders are not polled * until necessary. */ private static final class ConcatenatedAzureImagesLoader extends AzureImagesLoader { private final Queue delegates; ConcatenatedAzureImagesLoader( AzureImageEnumerationContext ctx, Collection azureImagesLoaders) { super(ctx); this.delegates = new LinkedList<>(azureImagesLoaders); } @Override public String toString() { return "Enumerating " + this.ctx.request.requestType + " images with " + getClass().getSimpleName(); } @Override public boolean hasNext() { while (!this.delegates.isEmpty()) { if (this.delegates.peek().hasNext()) { return true; } this.delegates.poll(); } return false; } @Override List nextPage() { return this.delegates.peek().next(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy