@SuppressWarnings("Guava")
public class AWSFilters {

  /**
   * A configuration key representing a blacklist.
   */
  public static final String BLACKLIST = "blacklist";

  /**
   * A configuration key representing a whitelist.
   */
  public static final String WHITELIST = "whitelist";

  /**
   * Empty filters.
   */
  public static final AWSFilters EMPTY_FILTERS =
      new AWSFilters(Optional.absent());

  /**
   * Returns AWS filters based on the specified configuration.
   *
   * @param config the underlying configuration
   * @return AWS filters based on the specified configuration
   */
  public static AWSFilters getAWSFilters(Config config) {
    return (config == null) ? EMPTY_FILTERS : new AWSFilters(Optional.of(config));
  }

  /**
   * The underlying configuration.
   */
  private Optional<Config> config;

  /**
   * Creates AWS filters with the specified parameters.
   *
   * @param config the underlying configuration
   */
  public AWSFilters(Optional<Config> config) {
    this.config = Preconditions.checkNotNull(config, "config is null");
  }

  /**
   * Returns the subfilters identified by the specified
   * sequence of keys.
   *
   * @param keys the keys
   * @return the subfilters identified by the specified
   * sequence of keys
   */
  public AWSFilters getSubfilters(String... keys) {
    if (config.isPresent()) {
      Config config = this.config.get();
      for (String key : keys) {
        try {
          config = config.getConfig(HoconConfigUtils.asConfigKey(key));
        } catch (ConfigException.Missing e) {
          return EMPTY_FILTERS;
        }
      }
      return getAWSFilters(config);
    } else {
      return EMPTY_FILTERS;
    }
  }

  /**
   * Returns the filter map for the specified key.
   *
   * @param key the key
   * @return the filter map for the specified key
   */
  public Map<String, String> getFilterMap(String key) {
    Map<String, String> filterMap;
    try {
      filterMap = config.isPresent()
          ? HoconConfigUtils.getStringMap(config.get(), HoconConfigUtils.asConfigKey(key))
          : Collections.emptyMap();
    } catch (Exception e) {
      filterMap = Collections.emptyMap();
    }
    return ImmutableMap.copyOf(filterMap);
  }

  /**
   * Returns whether the specified value is in the specified blacklist.
   *
   * @param key   the key identifying the specific blacklist to check
   * @param value the value to check
   * @return whether the specified value is in the specified blacklist
   */
  public boolean isBlacklisted(String key, String value) {
    return getBlacklistValue(key, value) != null;
  }

  /**
   * If the specified value is in the specified blacklist, returns the associated
   * blacklist value. Otherwise, returns {@code null}.
   *
   * @param key   the key identifying the specific blacklist to check
   * @param value the value to check
   * @return the blacklist value associated with the specified value in the specified blacklist,
   * or {@code null}
   */
  public String getBlacklistValue(String key, String value) {
    Map<String, String> blacklistMap = getSubfilters(key).getFilterMap(AWSFilters.BLACKLIST);
    return blacklistMap.get(value);
  }
} @SuppressWarnings("Guava")
public class AWSTimeouts {

  private final ImmutableMap<String, Long> timeouts;

  /**
   * Creates a new timeouts object from the given configuration. It is
   * expected that every value in the recursive tree of the configuration is
   * numeric. Each (recursive) key in the config serves as the key for a
   * timeout.
   *
   * @param config config holding timeouts
   * @throws IllegalArgumentException if any configuration value is non-numeric
   */
  public AWSTimeouts(Config config) {
    ImmutableMap.Builder<String, Long> b = ImmutableMap.builder();

    if (config != null) {
      for (Map.Entry<String, ConfigValue> e : config.entrySet()) {
        String key = e.getKey();
        ConfigValue value = e.getValue();
        switch (value.valueType()) {
          case NUMBER:
            long num = ((Number) value.unwrapped()).longValue();
            if (num <= 0L) {
              throw new IllegalArgumentException("Timeout " + key + " is negative: " + value);
            }
            b.put(key, num);
            break;
          default:
            throw new IllegalArgumentException("Timeout " + key + " is not a number: " + value);
        }
      }
    }

    timeouts = b.build();
  }

  /**
   * Gets a timeout value.
   *
   * @param key timeout key
   * @return timeout value as an Optional
   */
  public Optional<Long> getTimeout(String key) {
    return Optional.fromNullable(timeouts.get(key));
  }
} public class CustomTagMappings {

  private final ImmutableMap<String, String> customTagNames;

  /**
   * Creates a new custom tag mappings object from the given configuration. The keys of the
   * configuration are well-known tag names, and the values are the custom tag names.
   *
   * @param config config holding custom tags
   * @throws IllegalArgumentException if any configuration value is not a string or is empty
   */
  public CustomTagMappings(Config config) {
    ImmutableMap.Builder<String, String> b = ImmutableMap.builder();

    if (config != null) {
      for (Map.Entry<String, ConfigValue> e : config.entrySet()) {
        String key = e.getKey();
        ConfigValue value = e.getValue();
        switch (value.valueType()) {
          case STRING:
            String customTagName = (String) value.unwrapped();
            if (customTagName.isEmpty()) {
              throw new IllegalArgumentException("Tag mapping " + key + " is empty");
            }
            b.put(key, customTagName);
            break;
          default:
            throw new IllegalArgumentException("Tag mapping " + key + " is not a string: " + value);
        }
      }
    }

    customTagNames = b.build();
  }

  /**
   * Returns the custom tag name for the specified tag key if present, otherwise the key itself.
   *
   * @param tagKey tag key
   * @return the custom tag name for the specified tag key if present, otherwise the key itself
   */
  public String getCustomTagName(String tagKey) {
    return customTagNames.getOrDefault(tagKey, tagKey);
  }

  /**
   * Returns the name of the tag used to record the virtual instance ID as generated by Cloudera
   * Director.
   *
   * @return the name of the tag used to record the virtual instance ID as generated by Cloudera
   * Director
   */
  public String getClouderaDirectorIdTagName() {
    return getCustomTagName(Tags.ResourceTags.CLOUDERA_DIRECTOR_ID.getTagKey());
  }
} public final class Tags {

  /**
   * Director tags for cloud resources.
   */
  public enum ResourceTags {
    /**
     * The name of the resource.
     */
    RESOURCE_NAME("Name"),

    /**
     * The virtual instance ID as generated by Cloudera Altus Director.
     */
    CLOUDERA_DIRECTOR_ID("Cloudera-Director-Id"),

    /**
     * The virtual instance ID as generated by Cloudera Altus Director.
     */
    CLOUDERA_DIRECTOR_TEMPLATE_NAME("Cloudera-Director-Template-Name");

    private final String tagName;

    ResourceTags(String tagName) {
      this.tagName = tagName;
    }

    /**
     * Gets the tag's key.
     * Note that you should always call this method to get the tag's key, and not the
     * {@link ResourceTags#valueOf(String)} method.
     *
     * @return tag's key name
     */
    public String getTagKey() {
      return tagName;
    }
  }

  /**
   * Director tags specifically for cloud instances.
   */
  public enum InstanceTags {
    /**
     * The instance owner.
     */
    OWNER("owner");

    private final String tagName;

    InstanceTags(String tagName) {
      this.tagName = tagName;
    }

    /**
     * Gets the tag's key.
     * Note that you should always call this method to get the tag's key, and not the
     * {@link InstanceTags#valueOf(String)} method.
     *
     * @return tag's key name
     */
    public String getTagKey() {
      return tagName;
    }
  }


  /**
   * Private constructor to prevent instantiation.
   */
  private Tags() {
  }
} public interface ClientProvider<T> {

  /**
   * Returns a configured client.
   *
   * @param configuration               the provider configuration
   * @param accumulator                 the exception accumulator
   * @param providerLocalizationContext the resource provider localization context
   * @param verify                      whether to verify the configuration by making an API call
   * @return the configured client
   */
  T getClient(
      Configured configuration,
      PluginExceptionConditionAccumulator accumulator,
      LocalizationContext providerLocalizationContext,
      boolean verify);
} @SuppressWarnings({"PMD.TooManyStaticImports", "Guava"})
public class EphemeralDeviceMappings {

  private static final Logger LOG = LoggerFactory.getLogger(EphemeralDeviceMappings.class);

  public static class EphemeralDeviceMappingsConfigProperties extends DeviceMappingsConfigProperties {

    /**
     * Ephemeral device mappings configuration properties.
     */
    // Fully qualifying class name due to compiler bug
    @SuppressWarnings("PMD.UnnecessaryFullyQualifiedName")
    public enum EphemeralDeviceMappingsConfigurationPropertyToken
        implements com.cloudera.director.spi.v2.model.ConfigurationPropertyToken {
      /**
       * Path for the custom device mappings file. Relative paths are based on
       * the plugin configuration directory.
       */
      CUSTOM_MAPPINGS_PATH(new SimpleConfigurationPropertyBuilder()
          .configKey("customMappingsPath")
          .name("Custom mappings path")
          .defaultDescription("The path for the custom ephemeral device mappings file. Relative " +
              "paths are based on the plugin configuration directory.")
          .build());

      /**
       * The configuration property.
       */
      private final ConfigurationProperty configurationProperty;

      /**
       * Creates a configuration property token with the specified parameters.
       *
       * @param configurationProperty the configuration property
       */
      EphemeralDeviceMappingsConfigurationPropertyToken(
          ConfigurationProperty configurationProperty) {
        this.configurationProperty = configurationProperty;
      }

      @Override
      public ConfigurationProperty unwrap() {
        return configurationProperty;
      }
    }

    public static final String DEFAULT_DEVICE_NAME_PREFIX = "/dev/sd";

    /**
     * Ephemeral drives should be attached in the following range:
     * /dev/(sd|xvd)[b-y].
     */
    public static final char DEFAULT_RANGE_START_FOR_EPHEMERAL_DRIVES = 'b';

    public static final String DEFAULT_CUSTOM_MAPPINGS_PATH =
        "ec2.ephemeraldevicemappings.properties";

    private final File configurationDirectory;

    private String customMappingsPath = DEFAULT_CUSTOM_MAPPINGS_PATH;


    /**
     * Creates ephemeral device mappings config properties with the specified parameters.
     *
     * @param configuration            the configuration
     * @param configurationDirectory   the plugin configuration directory
     * @param cloudLocalizationContext the parent cloud localization context
     */
    public EphemeralDeviceMappingsConfigProperties(Configured configuration,
        File configurationDirectory,
        LocalizationContext cloudLocalizationContext) {
      super(Configurations.EPHEMERAL_DEVICE_MAPPINGS_SECTION, configuration, cloudLocalizationContext);
      this.configurationDirectory = configurationDirectory;
      LocalizationContext localizationContext = new ChildLocalizationContext(
          cloudLocalizationContext, Configurations.EPHEMERAL_DEVICE_MAPPINGS_SECTION);
      setCustomMappingsPath(configuration.getConfigurationValue(CUSTOM_MAPPINGS_PATH,
          localizationContext));
    }

    @Override
    public String getDefaultDeviceNamePrefix() {
      return DEFAULT_DEVICE_NAME_PREFIX;
    }

    @Override
    public char getDefaultRangeStart() {
      return DEFAULT_RANGE_START_FOR_EPHEMERAL_DRIVES;
    }

    public String getCustomMappingsPath() {
      return customMappingsPath;
    }

    File getCustomMappingsFile() {
      return getCustomMappingsFile(customMappingsPath);
    }

    File getCustomMappingsFile(String customMappingsPath) {
      File customMappingsPathFile = new File(customMappingsPath);
      return customMappingsPathFile.isAbsolute() ?
          customMappingsPathFile :
          new File(configurationDirectory, customMappingsPath);
    }

    public void setCustomMappingsPath(String customMappingsPath) {
      if (customMappingsPath != null) {
        LOG.info("Overriding customMappingsPath={} (default {})",
            customMappingsPath, DEFAULT_CUSTOM_MAPPINGS_PATH);
        checkArgument(getCustomMappingsFile(customMappingsPath).exists(),
            "Custom ephemeral device mappings path " +
                customMappingsPath + " does not exist");
        this.customMappingsPath = customMappingsPath;
      }
    }
  }

  public static class EphemeralDeviceMappingsConfig {

    public static final String BUILT_IN_LOCATION =
        "classpath:/com/cloudera/director/aws/ec2/ephemeraldevicemappings.properties";

    protected EphemeralDeviceMappingsConfigProperties
        ephemeralDeviceMappingsConfigProperties;

    public PropertyResolver ephemeralDeviceMappingsResolver() {
      try {
        return PropertyResolvers.newMultiResourcePropertyResolver(
            BUILT_IN_LOCATION,
            "file:" + ephemeralDeviceMappingsConfigProperties.getCustomMappingsFile().getAbsolutePath()
        );
      } catch (IOException e) {
        throw new IllegalArgumentException("Could not load ephemeral device mappings", e);
      }
    }

    /**
     * Creates an ephemeral device mappings config with the specified parameters.
     *
     * @param ephemeralDeviceMappingsConfigProperties the config properties
     */
    public EphemeralDeviceMappingsConfig(
        EphemeralDeviceMappingsConfigProperties ephemeralDeviceMappingsConfigProperties) {
      this.ephemeralDeviceMappingsConfigProperties = ephemeralDeviceMappingsConfigProperties;
    }
  }

  EphemeralDeviceMappingsConfigProperties ephemeralDeviceMappingsConfigProperties;

  PropertyResolver ephemeralDeviceMappingsResolver;

  /**
   * Creates ephemeral device mappings with the specified parameters.
   *
   * @param configuration               the configuration
   * @param configurationDirectory      the plugin configuration directory
   * @param launcherLocalizationContext the parent launcher localization context
   */
  public EphemeralDeviceMappings(Configured configuration, File configurationDirectory,
      LocalizationContext launcherLocalizationContext) {
    this(new EphemeralDeviceMappingsConfigProperties(configuration, configurationDirectory,
        launcherLocalizationContext));
  }

  /**
   * Creates ephemeral device mappings with the specified parameters.
   *
   * @param ephemeralDeviceMappingsConfigProperties the config properties
   */
  public EphemeralDeviceMappings(
      EphemeralDeviceMappingsConfigProperties ephemeralDeviceMappingsConfigProperties) {
    this(ephemeralDeviceMappingsConfigProperties,
        new EphemeralDeviceMappingsConfig(ephemeralDeviceMappingsConfigProperties)
            .ephemeralDeviceMappingsResolver());
  }

  /**
   * Creates ephemeral device mappings with the specified parameters.
   *
   * @param ephemeralDeviceMappingsConfigProperties the config properties
   * @param ephemeralDeviceMappingsResolver         the ephemeral device mappings resolver
   */
  private EphemeralDeviceMappings(
      EphemeralDeviceMappingsConfigProperties ephemeralDeviceMappingsConfigProperties,
      PropertyResolver ephemeralDeviceMappingsResolver) {
    this.ephemeralDeviceMappingsConfigProperties = ephemeralDeviceMappingsConfigProperties;
    this.ephemeralDeviceMappingsResolver = ephemeralDeviceMappingsResolver;
  }

  private final DeviceNameUtils deviceNameUtils = new DeviceNameUtils();

  /**
   * Generates a list of block device mappings for all ephemeral drives for
   * the given instance type.
   *
   * @param instanceType       EC2 instance type
   * @param excludeDeviceNames set of device names that shouldn't be used for the block device mappings
   * @return list of block device mappings
   * @see <a href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html">Block Device Mapping</a>
   */
  public List<BlockDeviceMapping> getBlockDeviceMappings(String instanceType, Set<String> excludeDeviceNames) {
    checkNotNull(instanceType, "instanceType is null");

    Optional<Integer> optCount = getCount(instanceType);
    if (!optCount.isPresent()) {
      LOG.error("Unsupported instance type {}, add its ephemeral instance " +
          "volume count as a custom mapping; assuming zero", instanceType);
      return Collections.emptyList();
    }

    int count = optCount.get();
    if (count == 0) {
      return Collections.emptyList();
    }

    List<String> deviceNames = deviceNameUtils.getDeviceNames(
        ephemeralDeviceMappingsConfigProperties.getDeviceNamePrefix(),
        ephemeralDeviceMappingsConfigProperties.getRangeStart(),
        count, excludeDeviceNames);

    List<BlockDeviceMapping> result = Lists.newArrayListWithExpectedSize(count);
    int index = 0;
    for (String device : deviceNames) {
      result.add(new BlockDeviceMapping()
          .withDeviceName(device)
          .withVirtualName("ephemeral" + index));
      index += 1;
    }

    return result;
  }

  public List<BlockDeviceMapping> getBlockDeviceMappings(String instanceType) {
    return getBlockDeviceMappings(instanceType, Collections.emptySet());
  }

  private Optional<Integer> getCount(String instanceType) {
    return Optional.fromNullable(
        ephemeralDeviceMappingsResolver.getProperty(instanceType, Integer.class)
    );
  }

  /**
   * Gets a test instance of this class that uses only the given mapping.
   *
   * @param counts                      map of instance types to counts
   * @param launcherLocalizationContext the parent launcher localization context
   * @return new mapping object
   */
  public static EphemeralDeviceMappings getTestInstance(Map<String, Integer> counts,
      LocalizationContext launcherLocalizationContext) {
    Map<String, String> propertyMap =
        Maps.transformValues(counts, Functions.toStringFunction());
    PropertyResolver ephemeralDeviceMappingsResolver =
        PropertyResolvers.newMapPropertyResolver(propertyMap);
    File tempDir = Files.createTempDir();
    tempDir.deleteOnExit();
    EphemeralDeviceMappingsConfigProperties ephemeralDeviceMappingsConfigProperties =
        new EphemeralDeviceMappingsConfigProperties(new SimpleConfiguration(),
            tempDir, launcherLocalizationContext);
    return new EphemeralDeviceMappings(ephemeralDeviceMappingsConfigProperties,
        ephemeralDeviceMappingsResolver);
  }
} public class VirtualizationMappings implements Function<String, List<String>> {

  private static final Logger LOG = LoggerFactory.getLogger(VirtualizationMappings.class);

  private static final Splitter SPLITTER = Splitter.on(',').trimResults();
  private static final Joiner JOINER = Joiner.on(',');

  public static class VirtualizationMappingsConfigProperties {

    /**
     * Virtualization mappings configuration properties.
     */
    // Fully qualifying class name due to compiler bug
    public enum VirtualizationMappingsConfigurationPropertyToken
        implements com.cloudera.director.spi.v2.model.ConfigurationPropertyToken {

      /**
       * Path for the custom virtualization mappings file. Relative paths are
       * based on the plugin configuration directory.
       */
      CUSTOM_MAPPINGS_PATH(new SimpleConfigurationPropertyBuilder()
          .configKey("customMappingsPath")
          .name("Custom mappings path")
          .defaultDescription("The path for the custom virtualization mappings file. Relative " +
              "paths are based on the plugin configuration directory.")
          .build());

      /**
       * The configuration property.
       */
      private final ConfigurationProperty configurationProperty;

      /**
       * Creates a configuration property token with the specified parameters.
       *
       * @param configurationProperty the configuration property
       */
      VirtualizationMappingsConfigurationPropertyToken(
          ConfigurationProperty configurationProperty) {
        this.configurationProperty = configurationProperty;
      }

      @Override
      public ConfigurationProperty unwrap() {
        return configurationProperty;
      }
    }

    public static final String DEFAULT_CUSTOM_MAPPINGS_PATH =
        "ec2.virtualizationmappings.properties";

    private final File configurationDirectory;

    private String customMappingsPath = DEFAULT_CUSTOM_MAPPINGS_PATH;

    /**
     * Creates virtualization mappings config properties with the specified parameters.
     *
     * @param configuration            the configuration
     * @param configurationDirectory   the plugin configuration directory
     * @param cloudLocalizationContext the parent cloud localization context
     */
    public VirtualizationMappingsConfigProperties(Configured configuration,
        File configurationDirectory,
        LocalizationContext cloudLocalizationContext) {
      this.configurationDirectory = configurationDirectory;
      LocalizationContext localizationContext = new ChildLocalizationContext(
          cloudLocalizationContext, "virtualizationMappings");
      setCustomMappingsPath(
          configuration.getConfigurationValue(CUSTOM_MAPPINGS_PATH, localizationContext));
    }

    public String getCustomMappingsPath() {
      return customMappingsPath;
    }

    File getCustomMappingsFile() {
      return getCustomMappingsFile(customMappingsPath);
    }

    File getCustomMappingsFile(String customMappingsPath) {
      File customMappingsPathFile = new File(customMappingsPath);
      return customMappingsPathFile.isAbsolute() ?
          customMappingsPathFile :
          new File(configurationDirectory, customMappingsPath);
    }

    public void setCustomMappingsPath(String customMappingsPath) {
      if (customMappingsPath != null) {
        LOG.info("Overriding customMappingsPath=%s (default {})",
            customMappingsPath, DEFAULT_CUSTOM_MAPPINGS_PATH);
        checkArgument(getCustomMappingsFile(customMappingsPath).exists(),
            "Custom virtualization mappings path " +
                customMappingsPath + " does not exist");
        this.customMappingsPath = customMappingsPath;
      }
    }
  }

  public static class VirtualizationMappingsConfig {

    public static final String BUILT_IN_LOCATION =
        "classpath:/com/cloudera/director/aws/ec2/virtualizationmappings.properties";

    protected VirtualizationMappingsConfigProperties
        virtualizationMappingsConfigProperties;

    public PropertyResolver virtualizationMappingsResolver() {
      try {
        return PropertyResolvers.newMultiResourcePropertyResolver(
            BUILT_IN_LOCATION,
            "file:" + virtualizationMappingsConfigProperties.getCustomMappingsFile().getAbsolutePath()
        );
      } catch (IOException e) {
        throw new IllegalArgumentException("Could not load custom virtualization mappings", e);
      }
    }

    /**
     * Creates an virtualization mappings config with the specified parameters.
     *
     * @param virtualizationMappingsConfigProperties the config properties
     */
    public VirtualizationMappingsConfig(
        VirtualizationMappingsConfigProperties virtualizationMappingsConfigProperties) {
      this.virtualizationMappingsConfigProperties = virtualizationMappingsConfigProperties;
    }
  }

  VirtualizationMappingsConfigProperties virtualizationMappingsConfigProperties;

  PropertyResolver virtualizationMappingsResolver;

  /**
   * Creates virtualization mappings with the specified parameters.
   *
   * @param configuration          the configuration
   * @param configurationDirectory the plugin configuration directory
   * @param localizationContext    the localization context
   */
  public VirtualizationMappings(Configured configuration, File configurationDirectory,
      LocalizationContext localizationContext) {
    this(new VirtualizationMappingsConfigProperties(configuration, configurationDirectory,
        localizationContext));
  }

  /**
   * Creates virtualization mappings with the specified parameters.
   *
   * @param virtualizationMappingsConfigProperties the config properties
   */
  public VirtualizationMappings(VirtualizationMappingsConfigProperties virtualizationMappingsConfigProperties) {
    this(virtualizationMappingsConfigProperties, new VirtualizationMappingsConfig(virtualizationMappingsConfigProperties).virtualizationMappingsResolver());
  }

  /**
   * Creates virtualization mappings with the specified parameters.
   *
   * @param virtualizationMappingsConfigProperties the config properties
   * @param virtualizationMappingsResolver         the virtualization mappings resolver
   */
  private VirtualizationMappings(VirtualizationMappingsConfigProperties virtualizationMappingsConfigProperties,
      PropertyResolver virtualizationMappingsResolver) {
    this.virtualizationMappingsConfigProperties = virtualizationMappingsConfigProperties;
    this.virtualizationMappingsResolver = virtualizationMappingsResolver;
  }

  /**
   * Gets the instance types that support the given virtualization type.
   *
   * @param virtualizationType virtualization type
   * @return list of instance types
   * @throws IllegalArgumentException if the virtualization type is unknown
   */
  @Nonnull
  public List<String> apply(String virtualizationType) {
    checkNotNull(virtualizationType, "virtualizationType is null");
    String instanceTypeList =
        virtualizationMappingsResolver.getProperty(virtualizationType);
    if (instanceTypeList == null) {
      throw new IllegalArgumentException("Unknown virtualization type " +
          virtualizationType);
    }
    return SPLITTER.splitToList(instanceTypeList);
  }

  /**
   * Gets a test instance of this class that uses only the given mapping.
   *
   * @param instanceTypes       map of virtualization types to instance types
   * @param localizationContext the localization context
   * @return new mapping object
   */
  public static VirtualizationMappings getTestInstance(
      final Map<String, List<String>> instanceTypes, LocalizationContext localizationContext) {
    Map<String, String> propertyMap =
        Maps.transformValues(instanceTypes, JOINER::join);
    PropertyResolver virtualizationMappingsResolver =
        PropertyResolvers.newMapPropertyResolver(propertyMap);
    File tempDir = Files.createTempDir();
    tempDir.deleteOnExit();
    VirtualizationMappingsConfigProperties virtualizationMappingsConfigProperties =
        new VirtualizationMappingsConfigProperties(new SimpleConfiguration(),
            tempDir, localizationContext);
    return new VirtualizationMappings(virtualizationMappingsConfigProperties,
        virtualizationMappingsResolver);
  }
} public enum IdType {
  /**
   * Director virtual instance ID.
   */
  VIRTUAL_INSTANCE_ID("virtual"),

  /**
   * EC2 instance ID.
   */
  EC2_INSTANCE_ID("EC2");

  /**
   * The description of IDs of this type.
   */
  private final String description;

  /**
   * Creates an ID type with the specified parameters.
   *
   * @param description the description of IDs of this type
   */
  IdType(String description) {
    this.description = description;
  }

  /**
   * Returns the description of IDs of this type.
   *
   * @return the description of IDs of this type
   */
  public String getDescription() {
    return description;
  }
} public class EBSDeviceMappings {

  private final DeviceNameUtils deviceNameUtils = new DeviceNameUtils();

  static final String DEFAULT_EBS_DEVICE_NAME_PREFIX = "/dev/sd";
  static final char DEFAULT_EBS_DEVICE_NAME_START_CHAR = 'f';

  public static class EbsDeviceMappingsConfigProperties extends DeviceMappingsConfigProperties {

    /**
     * Creates EBS device mappings config properties with the specified parameters.
     *
     * @param configSection            the plugin configuration section
     * @param configuration            the configuration
     * @param cloudLocalizationContext the parent cloud localization context
     */
    public EbsDeviceMappingsConfigProperties(String configSection, Configured configuration,
        LocalizationContext cloudLocalizationContext) {
      super(configSection, configuration, cloudLocalizationContext);
    }

    @Override
    public String getDefaultDeviceNamePrefix() {
      return DEFAULT_EBS_DEVICE_NAME_PREFIX;
    }

    @Override
    public char getDefaultRangeStart() {
      return DEFAULT_EBS_DEVICE_NAME_START_CHAR;
    }
  }

  private final EbsDeviceMappingsConfigProperties ebsDeviceMappingsConfigProperties;

  /**
   * Creates EBS device mappings with the specified configuration.
   *
   * @param configuration            the configuration
   * @param cloudLocalizationContext the parent cloud localization context
   */
  public EBSDeviceMappings(Configured configuration, LocalizationContext cloudLocalizationContext) {
    this.ebsDeviceMappingsConfigProperties = new EbsDeviceMappingsConfigProperties(
        Configurations.EBS_DEVICE_MAPPINGS_SECTION, configuration, cloudLocalizationContext);
  }

  /**
   * Gets a list of EBS block device mappings with the specified parameters.
   *
   * @param template     EC2InstanceTemplate which contains information about
   *                     the EBS volumes requested
   * @param excludeDeviceNames set device names that shouldn't be used for the block device mappings
   * @return set of EBS BlockDeviceMapping
   */
  @SuppressWarnings("Guava")
  public List<BlockDeviceMapping> getBlockDeviceMappings(EC2InstanceTemplate template,
                                                         Set<String> excludeDeviceNames) {
    List<SystemDisk> systemDisks = template.getSystemDisks();

    int templateDiskCount = template.getEbsVolumeCount();
    int systemDiskCount = systemDisks.size();
    int totalVolumeCount = templateDiskCount + systemDiskCount;

    List<String> deviceNames = getDeviceNames(totalVolumeCount, excludeDeviceNames);

    List<BlockDeviceMapping> mappings = Lists.newArrayList();

    for (int i = 0; i < systemDisks.size(); i++) {
      String deviceName = deviceNames.get(i);
      SystemDisk systemDisk = systemDisks.get(i);
      mappings.add(systemDisk.toBlockDeviceMapping(deviceName));
    }

    for (int i = 0; i < templateDiskCount; i++) {
      String deviceName = deviceNames.get(i + systemDiskCount);

      EbsBlockDevice ebs = new EbsBlockDevice()
          .withVolumeType(template.getEbsVolumeType())
          .withVolumeSize(template.getEbsVolumeSizeGiB())
          .withEncrypted(template.isEnableEbsEncryption())
          .withDeleteOnTermination(true);

      Optional<Integer> iops = template.getEbsIops();
      Optional<String> kmsKeyid = template.getEbsKmsKeyId();

      if (iops.isPresent()) {
        ebs = ebs.withIops(iops.get());
      }
      if (kmsKeyid.isPresent()) {
        ebs = ebs.withKmsKeyId(kmsKeyid.get());
      }

      BlockDeviceMapping mapping = new BlockDeviceMapping()
          .withDeviceName(deviceName)
          .withEbs(ebs);

      mappings.add(mapping);
    }

    return mappings;
  }

  List<String> getDeviceNames(int count, Set<String> filter) {
    return deviceNameUtils.getDeviceNames(
        ebsDeviceMappingsConfigProperties.getDeviceNamePrefix(),
        ebsDeviceMappingsConfigProperties.getRangeStart(),
        count, filter
    );
  }


  /**
   * Gets an instance of this class that uses only the given EBS device mapping configuration.
   *
   * @param configuration       EBS device mapping configuration
   * @param localizationContext the localization context
   * @return new EBS metadata object
   */
  public static EBSDeviceMappings getDefaultInstance(
      final Map<String, String> configuration, LocalizationContext localizationContext) {
    return new EBSDeviceMappings(new SimpleConfiguration(configuration), localizationContext);
  }
} @SuppressWarnings("PMD.UnusedPrivateField")
public class EBSMetadata implements Function<String, EBSMetadata.EbsVolumeMetadata> {

  private static final Logger LOG = LoggerFactory.getLogger(EBSMetadata.class);

  /**
   * Contains metadata information for an EBS volume type.
   */
  @SuppressWarnings("Guava")
  public static class EbsVolumeMetadata {
    private final String type;
    private final int minSizeGiB;
    private final int maxSizeGiB;
    private final Optional<Integer> minIops;
    private final Optional<Integer> maxIops;

    public EbsVolumeMetadata(String type, int minSizeGiB, int maxSizeGiB) {
      this.type = Objects.requireNonNull(type, "type is null");
      this.minSizeGiB = minSizeGiB;
      this.maxSizeGiB = maxSizeGiB;
      this.minIops = Optional.absent();
      this.maxIops = Optional.absent();
    }

    public EbsVolumeMetadata(String type, int minSizeGiB, int maxSizeGiB, int minIops, int maxIops) {
      this.type = Objects.requireNonNull(type, "type is null");
      this.minSizeGiB = minSizeGiB;
      this.maxSizeGiB = maxSizeGiB;
      this.minIops = Optional.of(minIops);
      this.maxIops = Optional.of(maxIops);
    }

    public String getType() {
      return type;
    }

    public int getMinSizeGiB() {
      return minSizeGiB;
    }

    public int getMaxSizeGiB() {
      return maxSizeGiB;
    }

    public Optional<Integer> getMinIops() {
      return minIops;
    }

    public Optional<Integer> getMaxIops() {
      return maxIops;
    }

    @SuppressWarnings("SimplifiableIfStatement")
    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      EbsVolumeMetadata that = (EbsVolumeMetadata) o;

      if (minSizeGiB != that.minSizeGiB) return false;
      if (maxSizeGiB != that.maxSizeGiB) return false;
      if (!type.equals(that.type)) return false;
      if (!minIops.equals(that.minIops)) return false;
      return maxIops.equals(that.maxIops);
    }

    @Override
    public int hashCode() {
      int result = type.hashCode();
      result = 31 * result + minSizeGiB;
      result = 31 * result + maxSizeGiB;
      result = 31 * result + minIops.hashCode();
      result = 31 * result + maxIops.hashCode();
      return result;
    }
  }

  public static class EBSMetadataConfigProperties {

    /**
     * EBS metadata configuration properties.
     */
    // Fully qualifying class name due to compiler bug
    public enum EBSMetadataConfigurationPropertyToken
        implements com.cloudera.director.spi.v2.model.ConfigurationPropertyToken {

      /**
       * Path for the custom EBS metadata file. Relative paths are based on the
       * plugin configuration directory.
       */
      CUSTOM_EBS_METADATA_PATH(new SimpleConfigurationPropertyBuilder()
          .configKey("ebsMetadataPath")
          .name("Custom EBS metadata path")
          .defaultDescription("The path for the custom ebs metadata. Relative paths are based " +
              "on the plugin configuration directory.")
          .build());

      /**
       * The configuration property.
       */
      private final ConfigurationProperty configurationProperty;

      /**
       * Creates a configuration property token with the specified parameters.
       *
       * @param configurationProperty the configuration property
       */
      EBSMetadataConfigurationPropertyToken(
          ConfigurationProperty configurationProperty) {
        this.configurationProperty = configurationProperty;
      }

      @Override
      public ConfigurationProperty unwrap() {
        return configurationProperty;
      }
    }

    public static final String DEFAULT_CUSTOM_EBS_METADATA_PATH =
        "ec2.ebs.ebsmetadata.properties";

    private final File configurationDirectory;

    private String ebsMetadataPath = DEFAULT_CUSTOM_EBS_METADATA_PATH;

    /**
     * Creates EBS metadata config properties with the specified parameters.
     *
     * @param configuration            the configuration
     * @param configurationDirectory   the plugin configuration directory
     * @param cloudLocalizationContext the parent cloud localization context
     */
    public EBSMetadataConfigProperties(Configured configuration,
        File configurationDirectory,
        LocalizationContext cloudLocalizationContext) {
      this.configurationDirectory = configurationDirectory;
      LocalizationContext localizationContext = new ChildLocalizationContext(
          cloudLocalizationContext, "ebsMetadata");
      setCustomEbsMetadataPath(
          configuration.getConfigurationValue(EBSMetadataConfigurationPropertyToken.CUSTOM_EBS_METADATA_PATH,
              localizationContext));
    }

    public String getCustomEbsMetadataPath() {
      return ebsMetadataPath;
    }

    File getCustomEbsMetadataFile() {
      return getCustomEbsMetadataFile(ebsMetadataPath);
    }

    File getCustomEbsMetadataFile(String ebsMetadataPath) {
      File ebsMetadataPathFile = new File(ebsMetadataPath);
      return ebsMetadataPathFile.isAbsolute() ?
          ebsMetadataPathFile :
          new File(configurationDirectory, ebsMetadataPath);
    }

    public void setCustomEbsMetadataPath(String ebsMetadataPath) {
      if (ebsMetadataPath != null) {
        LOG.info("Overriding ebsMetadataPath={} (default {})",
            ebsMetadataPath, DEFAULT_CUSTOM_EBS_METADATA_PATH);
        checkArgument(getCustomEbsMetadataFile(ebsMetadataPath).exists(),
            "Custom EBS metadata path " +
                ebsMetadataPath + " does not exist");
        this.ebsMetadataPath = ebsMetadataPath;
      }
    }
  }

  public static class EBSMetadataConfig {

    public static final String BUILT_IN_LOCATION =
        "classpath:/com/cloudera/director/aws/ec2/ebs/ebsmetadata.properties";

    protected EBSMetadataConfigProperties ebsMetadataConfigProperties;

    public PropertyResolver ebsMetadataResolver() {
      try {
        return PropertyResolvers.newMultiResourcePropertyResolver(
            BUILT_IN_LOCATION,
            "file:" + ebsMetadataConfigProperties.getCustomEbsMetadataFile().getAbsolutePath()
        );
      } catch (IOException e) {
        throw new IllegalArgumentException("Could not load custom ebs metadata", e);
      }
    }

    /**
     * Creates EBS metadata config with the specified parameters.
     *
     * @param ebsMetadataConfigProperties the config properties
     */
    public EBSMetadataConfig(EBSMetadataConfigProperties ebsMetadataConfigProperties) {
      this.ebsMetadataConfigProperties = ebsMetadataConfigProperties;
    }
  }

  private final EBSMetadataConfigProperties ebsMetadataConfigProperties;

  private final PropertyResolver ebsMetadataResolver;

  /**
   * Creates EBS metadata with the specified parameters.
   *
   * @param configuration          the configuration
   * @param configurationDirectory the plugin configuration directory
   * @param localizationContext    the localization context
   */
  public EBSMetadata(Configured configuration, File configurationDirectory,
      LocalizationContext localizationContext) {
    this(new EBSMetadataConfigProperties(configuration, configurationDirectory,
        localizationContext));
  }

  /**
   * Creates EBS metadata with the specified parameters.
   *
   * @param ebsMetadataConfigProperties the config properties
   */
  public EBSMetadata(EBSMetadataConfigProperties ebsMetadataConfigProperties) {
    this(ebsMetadataConfigProperties, new EBSMetadataConfig(ebsMetadataConfigProperties).ebsMetadataResolver());
  }

  /**
   * Creates EBS metadata with the specified parameters.
   *
   * @param ebsMetadataConfigProperties the config properties
   * @param ebsMetadataResolver         the ebs metadata resolver
   */
  private EBSMetadata(EBSMetadataConfigProperties ebsMetadataConfigProperties,
      PropertyResolver ebsMetadataResolver) {
    this.ebsMetadataConfigProperties = ebsMetadataConfigProperties;
    this.ebsMetadataResolver = ebsMetadataResolver;
  }

  /**
   * Gets the metadata for an EBS volume type. A custom definition overrides a
   * built-in definition.
   *
   * @param volumeType ebs volume type
   * @return metadata associated with the volume type
   * @throws NullPointerException  if no metadata could be found for a volume type
   * @throws IllegalStateException if the metadata for a volume type has invalid format
   */
  @Override
  public EbsVolumeMetadata apply(String volumeType) {
    String strCapacityRange = ebsMetadataResolver.getProperty(volumeType);
    Objects.requireNonNull(strCapacityRange, String.format("Could not get metadata for volume type %s", volumeType));

    Range capacityRange = Range.resolveRange(volumeType, strCapacityRange);
    int minSizeGiB = capacityRange.getMin();
    int maxSizeGiB = capacityRange.getMax();

    if (volumeType.equals("io1")) {
      String key = "io1-iops";
      String strIopsMetadata = ebsMetadataResolver.getProperty(key);
      Objects.requireNonNull(strIopsMetadata, String.format("Could not get metadata for %s", key));

      Range iopsRange = Range.resolveRange(key, strIopsMetadata);
      return new EbsVolumeMetadata(volumeType, minSizeGiB, maxSizeGiB, iopsRange.getMin(), iopsRange.getMax());
    }

    return new EbsVolumeMetadata(volumeType, minSizeGiB, maxSizeGiB);
  }

  private static class Range {
    private int min;
    private int max;

    private Range(int min, int max) {
      this.min = min;
      this.max = max;
    }

    public int getMin() {
      return min;
    }

    public int getMax() {
      return max;
    }

    static Range resolveRange(String key, String rangeStr) {

      // range string is expected to be in the form "minValue-maxValue"

      String[] parts = rangeStr.trim().split("-");
      checkState(parts.length == 2, String.format("invalid format for %s=%s, expecting " +
          "exactly 2 parts for the value", key, rangeStr));

      int min, max;
      try {
        min = Integer.parseInt(parts[0]);
      } catch (NumberFormatException ex) {
        throw new IllegalStateException(
            String.format("invalid format for %s=%s, %s should be an integer", key, rangeStr, parts[0])
        );
      }

      try {
        max = Integer.parseInt(parts[1]);
      } catch (NumberFormatException ex) {
        throw new IllegalStateException(
            String.format("invalid format for %s=%s, %s should be an integer", key, rangeStr, parts[1])
        );
      }

      checkState(min > 0, String.format("invalid format for %s=%s, %d should " +
          "be a positive integer", key, rangeStr, min));

      checkState(max > 0, String.format("invalid format for %s=%s, %d should " +
          "be a positive integer", key, rangeStr, max));

      checkState(max >= min, String.format("invalid format for %s=%s, maximum size %d should " +
          "be greater than or equal to minimum size %d", key, rangeStr, max, min));

      return new Range(min, max);
    }
  }


  /**
   * Gets an instance of this class that uses only the given ebs metadata.
   *
   * @param metadata            map of ebs volume type to it's associated metadata
   * @param localizationContext the localization context
   * @return new ebs metadata object
   */
  public static EBSMetadata getDefaultInstance(
      final Map<String, String> metadata, LocalizationContext localizationContext) {
    PropertyResolver ebsMetadataResolver =
        PropertyResolvers.newMapPropertyResolver(metadata);
    File tempDir = Files.createTempDir();
    tempDir.deleteOnExit();
    EBSMetadataConfigProperties ebsMetadataConfigProperties =
        new EBSMetadataConfigProperties(new SimpleConfiguration(), tempDir, localizationContext);
    return new EBSMetadata(ebsMetadataConfigProperties, ebsMetadataResolver);
  }
} public final class NetworkRules {

  private static final Logger LOG = LoggerFactory.getLogger(NetworkRules.class);

  /**
   * The root key for network rules config.
   */
  private static final String ROOT_KEY = NetworkRules.class.getCanonicalName();

  /**
   * The table holding all network rules.
   */
  private final Table<Direction, AccessType, Iterable<NetworkRule>> rules;

  /**
   * A constant represents an empty set of network rules.
   */
  public static final NetworkRules EMPTY_RULES
      = new NetworkRules(ImmutableTable.of());

  /**
   * Internal constructor.
   *
   * @param rules a rules table
   */
  private NetworkRules(Table<Direction, AccessType, Iterable<NetworkRule>> rules) {
    Preconditions.checkNotNull(rules, "rules is null");
    this.rules = rules;
  }

  /**
   * Constructs a NetworkRules object using the given HOCON config.
   *
   * @param config a HOCON config
   * @return a NetworkRules object
   */
  public static NetworkRules fromConfig(Config config) {
    if (config == null) {
      return EMPTY_RULES;
    }

    try {
      return new NetworkRules(parse(config.getConfig(ROOT_KEY)));
    } catch (ConfigException | IllegalArgumentException e) {
      String msg = "Failed to parse network rules";
      LOG.error(msg, e);
      throw new IllegalArgumentException(msg, e);
    }
  }

  /**
   * Parses a HOCON config to a table of network rules.
   */
  private static Table<Direction, AccessType, Iterable<NetworkRule>> parse(Config config) {
    Table<Direction, AccessType, Iterable<NetworkRule>> rules =
        ArrayTable.create(EnumSet.allOf(Direction.class), EnumSet.allOf(AccessType.class));
    for (Direction direction : Direction.values()) {
      for (AccessType access : AccessType.values()) {
        populateRules(config, direction, access, rules);
      }
    }
    return rules;
  }

  /**
   * Populates the rule table.
   *
   * @param config    the HOCON config
   * @param direction the network direction
   * @param access    the network rule access type
   * @param rules     the rules table
   */
  private static void populateRules(Config config, Direction direction, AccessType access,
      Table<Direction, AccessType, Iterable<NetworkRule>> rules) {
    List<? extends Config> configList =
        config.getConfig(direction.toString().toLowerCase()).
            getConfigList(access.toString().toLowerCase());
    Set<NetworkRule> cell = Sets.newHashSet();
    for (Config val : configList) {
      Iterables.addAll(cell, NetworkRule.fromConfig(val, access));
    }
    rules.put(direction, access, cell);
  }

  /**
   * Returns the number of network rules.
   *
   * @return the number of network rules
   */
  public int size() {
    return this.rules.size();
  }

  /**
   * Checks if the network rule set is empty.
   *
   * @return true if it contains no network rules
   */
  public boolean isEmpty() {
    return this.rules.isEmpty();
  }

  /**
   * Retrieves rules based on the direction of network traffic.
   * <p>
   * <p>The returned rules are just copies of actual network rules. Changes to the
   * returned collection will not update the underlying NetworkRules, and vice versa.
   *
   * @param direction the network direction
   * @return the associated list of network rules
   */
  public Iterable<NetworkRule> getRules(Direction direction) {
    if (this.rules.isEmpty()) {
      return Collections.emptySet();
    }
    Set<NetworkRule> ruleSet = Sets.newHashSet();
    for (AccessType access : AccessType.values()) {
      Iterables.addAll(ruleSet, getRules(direction, access));
    }
    return ruleSet;
  }

  /**
   * Retrieves rules based on the network direction and access type.
   * <p>
   * <p>The returned rules are just copies of actual network rules. Changes to the
   * returned collection will not update the underlying NetworkRules, and vice versa.
   *
   * @param direction the network direction
   * @param access    the network rule access type
   * @return the associated list of network rules
   */
  public Iterable<NetworkRule> getRules(Direction direction, AccessType access) {
    return this.rules.isEmpty()
        ? Collections.emptySet()
        : Sets.newHashSet(this.rules.get(direction, access));
  }
}
import static com.cloudera.director.spi.v2.provider.Launcher.DEFAULT_PLUGIN_LOCALIZATION_CONTEXT;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import com.cloudera.director.aws.AWSFilters;
import com.cloudera.director.aws.AWSTimeouts;
import com.cloudera.director.aws.CustomTagMappings;
import com.cloudera.director.aws.Tags;
import com.cloudera.director.aws.clientprovider.ClientProvider;
import com.cloudera.director.aws.ec2.EphemeralDeviceMappings;
import com.cloudera.director.aws.ec2.VirtualizationMappings;
import com.cloudera.director.aws.ec2.allocation.IdType;
import com.cloudera.director.aws.ec2.ebs.EBSDeviceMappings;
import com.cloudera.director.aws.ec2.ebs.EBSMetadata;
import com.cloudera.director.aws.network.NetworkRules;
import com.cloudera.director.aws.shaded.com.amazonaws.services.autoscaling.AmazonAutoScalingAsyncClient;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.AmazonEC2AsyncClient;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.BlockDeviceMapping;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.DescribeInstanceStatusRequest;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.DescribeInstanceStatusResult;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.EbsBlockDevice;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.Instance;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.InstanceState;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.InstanceStateName;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.InstanceStatus;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.Reservation;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.StateReason;
import com.cloudera.director.aws.shaded.com.amazonaws.services.ec2.model.Tag;
import com.cloudera.director.aws.shaded.com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient;
import com.cloudera.director.aws.shaded.com.amazonaws.services.kms.AWSKMSClient;
import com.cloudera.director.aws.shaded.com.amazonaws.services.securitytoken.AWSSecurityTokenServiceAsyncClient;
import com.cloudera.director.aws.shaded.com.google.common.base.Function;
import com.cloudera.director.aws.shaded.com.google.common.base.Predicate;
import com.cloudera.director.aws.shaded.com.typesafe.config.ConfigFactory;
import com.cloudera.director.aws.shaded.org.joda.time.DateTime;
import com.cloudera.director.aws.shaded.org.joda.time.Duration;
import com.cloudera.director.spi.v2.model.ConfigurationPropertyToken;
import com.cloudera.director.spi.v2.model.Configured;
import com.cloudera.director.spi.v2.model.LocalizationContext;
import com.cloudera.director.spi.v2.model.exception.PluginExceptionConditionAccumulator;
import com.cloudera.director.spi.v2.model.util.SimpleConfiguration;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
// (c) Copyright 2016 Cloudera, 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.cloudera.director.aws.ec2.provider;





public class EC2ProviderTest {

  private static final Logger LOG = Logger.getLogger(EC2ProviderTest.class.getName());

  private static void putConfig(Map<String, String> configMap, ConfigurationPropertyToken propertyToken,
      String value) {
    if (value != null) {
      configMap.put(propertyToken.unwrap().getConfigKey(), value);
    }
  }

  @Rule
  public ExpectedException expectedException = ExpectedException.none();

  private EC2Provider ec2Provider;
  private AmazonEC2AsyncClient ec2Client;
  private AmazonAutoScalingAsyncClient autoScalingClient;
  private AmazonIdentityManagementClient identityManagementClient;
  private AWSKMSClient kmsClient;
  private AWSSecurityTokenServiceAsyncClient stsClient;

  private BlockDeviceMapping ebs1, ebs1x, ebs2;
  private BlockDeviceMapping eph2;

  @Before
  public void setUp() {
    setupEC2Provider();
    setupBlockDeviceMappings();
  }

  @SuppressWarnings("unchecked")
  private void setupEC2Provider() {
    // Configure ephemeral device mappings
    EphemeralDeviceMappings ephemeralDeviceMappings =
        EphemeralDeviceMappings.getTestInstance(ImmutableMap.of("m3.medium", 1),
            DEFAULT_PLUGIN_LOCALIZATION_CONTEXT);

    // Configure ebs device mappings
    EBSDeviceMappings ebsDeviceMappings =
        EBSDeviceMappings.getDefaultInstance(ImmutableMap.<String, String>of(),
            DEFAULT_PLUGIN_LOCALIZATION_CONTEXT);

    // Configure ebs metadata
    EBSMetadata ebsMetadata =
        EBSMetadata.getDefaultInstance(ImmutableMap.of("st1", "500-16384"),
            DEFAULT_PLUGIN_LOCALIZATION_CONTEXT);

    // Configure virtualization mappings
    VirtualizationMappings virtualizationMappings =
        VirtualizationMappings.getTestInstance(ImmutableMap.of("paravirtual", Arrays.asList("m3.medium")),
            DEFAULT_PLUGIN_LOCALIZATION_CONTEXT);

    // Configure filters and timeouts
    AWSFilters awsFilters = AWSFilters.EMPTY_FILTERS;