def _PostCreate(self):
    """Get the instance's data and tag it."""
    describe_cmd = util.AWS_PREFIX + [
        'ec2',
        'describe-instances',
        '--region=%s' % self.region,
        '--instance-ids=%s' % self.id]
    logging.info('Getting instance %s public IP. This will fail until '
                 'a public IP is available, but will be retried.', self.id)
    stdout, _ = util.IssueRetryableCommand(describe_cmd)
    response = json.loads(stdout)
    instance = response['Reservations'][0]['Instances'][0]
    self.ip_address = instance['PublicIpAddress']
    self.internal_ip = instance['PrivateIpAddress']
    if util.IsRegion(self.zone):
      self.zone = str(instance['Placement']['AvailabilityZone'])

    assert self.group_id == instance['SecurityGroups'][0]['GroupId'], (
        self.group_id, instance['SecurityGroups'][0]['GroupId'])
    if FLAGS.aws_efa and FLAGS.aws_efa_version:
      # Download EFA then call InstallEfa method so that subclass can override
      self.InstallPackages('curl')
      url = _EFA_URL.format(version=FLAGS.aws_efa_version)
      tarfile = posixpath.basename(url)
      self.RemoteCommand(f'curl -O {url}; tar -xvzf {tarfile}')
      self._InstallEfa()
      # Run test program to confirm EFA working
      self.RemoteCommand('cd aws-efa-installer; ./efa_test.sh')
Пример #2
0
 def __init__(self, spec):
     super(EksCluster, self).__init__(spec)
     # EKS requires a region and optionally a list of one or zones.
     # Interpret the zone as a comma separated list of zones or a region.
     self.control_plane_zones = self.zone and self.zone.split(',')
     if not self.control_plane_zones:
         raise errors.Config.MissingOption(
             'container_cluster.vm_spec.AWS.zone is required.')
     elif len(self.control_plane_zones) == 1 and util.IsRegion(self.zone):
         self.region = self.zone
         self.control_plane_zones = []
         logging.info("Interpreting zone '%s' as a region", self.zone)
     else:
         self.region = util.GetRegionFromZones(self.control_plane_zones)
     # control_plane_zones must be a superset of the node zones
     for nodepool in self.nodepools.values():
         if nodepool.vm_config.zone not in self.control_plane_zones:
             self.control_plane_zones.append(nodepool.vm_config.zone)
     if len(self.control_plane_zones) == 1:
         # eksctl essentially requires you pass --zones if you pass --node-zones
         # and --zones must have at least 2 zones
         # https://github.com/weaveworks/eksctl/issues/4735
         self.control_plane_zones.append(
             self.region + ('b' if self.zone.endswith('a') else 'a'))
     self.cluster_version = FLAGS.container_cluster_version
     # TODO(user) support setting boot disk type if EKS does.
     self.boot_disk_type = self.vm_config.DEFAULT_ROOT_DISK_TYPE
    def _Create(self):
        """Create a VM instance."""
        placement = []
        if not util.IsRegion(self.zone):
            placement.append('AvailabilityZone=%s' % self.zone)
        if IsPlacementGroupCompatible(self.machine_type):
            placement.append('GroupName=%s' %
                             self.network.placement_group.name)
        placement = ','.join(placement)
        block_device_map = GetBlockDeviceMap(self.machine_type)

        create_cmd = util.AWS_PREFIX + [
            'ec2', 'run-instances',
            '--region=%s' % self.region,
            '--subnet-id=%s' % self.network.subnet.id,
            '--associate-public-ip-address',
            '--image-id=%s' % self.image,
            '--instance-type=%s' % self.machine_type,
            '--key-name=%s' % 'perfkit-key-%s' % FLAGS.run_uri
        ]
        if block_device_map:
            create_cmd.append('--block-device-mappings=%s' % block_device_map)
        if placement:
            create_cmd.append('--placement=%s' % placement)
        if self.user_data:
            create_cmd.append('--user-data=%s' % self.user_data)
        stdout, _, _ = vm_util.IssueCommand(create_cmd)
        response = json.loads(stdout)
        self.id = response['Instances'][0]['InstanceId']
    def __init__(self, vm_spec):
        """Initialize a AWS virtual machine.

    Args:
      vm_spec: virtual_machine.BaseVirtualMachineSpec object of the vm.
    """
        super(AwsVirtualMachine, self).__init__(vm_spec)
        self.region = util.GetRegionFromZone(self.zone)
        self.user_name = FLAGS.aws_user_name
        if self.machine_type in NUM_LOCAL_VOLUMES:
            self.max_local_disks = NUM_LOCAL_VOLUMES[self.machine_type]
        self.user_data = None
        self.network = aws_network.AwsNetwork.GetNetwork(self)
        self.firewall = aws_network.AwsFirewall.GetFirewall()
        self.use_dedicated_host = vm_spec.use_dedicated_host
        self.use_spot_instance = vm_spec.use_spot_instance
        self.spot_price = vm_spec.spot_price
        self.boot_disk_size = vm_spec.boot_disk_size
        self.client_token = str(uuid.uuid4())
        self.host = None
        self.id = None

        if self.use_dedicated_host and util.IsRegion(self.zone):
            raise ValueError(
                'In order to use dedicated hosts, you must specify an availability '
                'zone, not a region ("zone" was %s).' % self.zone)

        if self.use_spot_instance and self.spot_price <= 0.0:
            raise ValueError(
                'In order to use spot instances you must specify a spot price '
                'greater than 0.0.')
  def __init__(self, vm_spec):
    """Initialize a AWS virtual machine.

    Args:
      vm_spec: virtual_machine.BaseVirtualMachineSpec object of the vm.

    Raises:
      ValueError: If an incompatible vm_spec is passed.
    """
    super(AwsVirtualMachine, self).__init__(vm_spec)
    self.region = util.GetRegionFromZone(self.zone)
    self.user_name = FLAGS.aws_user_name or self.DEFAULT_USER_NAME
    if self.machine_type in aws_disk.NUM_LOCAL_VOLUMES:
      self.max_local_disks = aws_disk.NUM_LOCAL_VOLUMES[self.machine_type]
    self.user_data = None
    self.network = aws_network.AwsNetwork.GetNetwork(self)
    self.placement_group = getattr(vm_spec, 'placement_group',
                                   self.network.placement_group)
    self.firewall = aws_network.AwsFirewall.GetFirewall()
    self.use_dedicated_host = vm_spec.use_dedicated_host
    self.num_vms_per_host = vm_spec.num_vms_per_host
    self.use_spot_instance = vm_spec.use_spot_instance
    self.spot_price = vm_spec.spot_price
    self.spot_block_duration_minutes = vm_spec.spot_block_duration_minutes
    self.boot_disk_size = vm_spec.boot_disk_size
    self.client_token = str(uuid.uuid4())
    self.host = None
    self.id = None
    self.metadata.update({
        'spot_instance':
            self.use_spot_instance,
        'spot_price':
            self.spot_price,
        'spot_block_duration_minutes':
            self.spot_block_duration_minutes,
        'placement_group_strategy':
            self.placement_group.strategy
            if self.placement_group else placement_group.PLACEMENT_GROUP_NONE,
        'aws_credit_specification':
            FLAGS.aws_credit_specification
            if FLAGS.aws_credit_specification else 'none'
    })
    self.spot_early_termination = False
    self.spot_status_code = None
    # See:
    # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking-os.html
    self._smp_affinity_script = 'smp_affinity.sh'

    if self.use_dedicated_host and util.IsRegion(self.zone):
      raise ValueError(
          'In order to use dedicated hosts, you must specify an availability '
          'zone, not a region ("zone" was %s).' % self.zone)

    if self.use_dedicated_host and self.use_spot_instance:
      raise ValueError(
          'Tenancy=host is not supported for Spot Instances')
    def __init__(self, vm_spec):
        """Initialize a AWS virtual machine.

    Args:
      vm_spec: virtual_machine.BaseVirtualMachineSpec object of the vm.

    Raises:
      ValueError: If an incompatible vm_spec is passed.
    """
        super(AwsVirtualMachine, self).__init__(vm_spec)
        self.region = util.GetRegionFromZone(self.zone)
        self.user_name = FLAGS.aws_user_name
        if self.machine_type in NUM_LOCAL_VOLUMES:
            self.max_local_disks = NUM_LOCAL_VOLUMES[self.machine_type]
        self.user_data = None
        self.network = aws_network.AwsNetwork.GetNetwork(self)
        self.placement_group = getattr(vm_spec, 'placement_group',
                                       self.network.placement_group)
        self.firewall = aws_network.AwsFirewall.GetFirewall()
        self.use_dedicated_host = vm_spec.use_dedicated_host
        # TODO(buggay): implement num_vms_per_host functionality
        self.num_vms_per_host = vm_spec.num_vms_per_host
        if self.num_vms_per_host:
            raise NotImplementedError(
                'Num vms per host for AWS is not supported.')
        self.use_spot_instance = vm_spec.use_spot_instance
        self.spot_price = vm_spec.spot_price
        self.boot_disk_size = vm_spec.boot_disk_size
        self.client_token = str(uuid.uuid4())
        self.host = None
        self.id = None
        self.metadata.update({
            'spot_instance':
            self.use_spot_instance,
            'spot_price':
            self.spot_price,
            'placement_group_strategy':
            self.placement_group.strategy if self.placement_group else 'none',
            'aws_credit_specification':
            FLAGS.aws_credit_specification
            if FLAGS.aws_credit_specification else 'none'
        })
        self.early_termination = False
        self.spot_status_code = None

        if self.use_dedicated_host and util.IsRegion(self.zone):
            raise ValueError(
                'In order to use dedicated hosts, you must specify an availability '
                'zone, not a region ("zone" was %s).' % self.zone)

        if self.use_dedicated_host and self.use_spot_instance:
            raise ValueError(
                'Tenancy=host is not supported for Spot Instances')
Пример #7
0
  def _Create(self):
    """Creates the subnet."""

    create_cmd = util.AWS_PREFIX + [
        'ec2',
        'create-subnet',
        '--region=%s' % self.region,
        '--vpc-id=%s' % self.vpc_id,
        '--cidr-block=%s' % self.cidr_block]
    if not util.IsRegion(self.zone):
      create_cmd.append('--availability-zone=%s' % self.zone)

    stdout, _, _ = vm_util.IssueCommand(create_cmd)
    response = json.loads(stdout)
    self.id = response['Subnet']['SubnetId']
    util.AddDefaultTags(self.id, self.region)
Пример #8
0
 def _Create(self):
     """Creates the disk."""
     create_cmd = util.AWS_PREFIX + [
         'ec2', 'create-volume',
         '--region=%s' % self.region,
         '--size=%s' % self.disk_size,
         '--volume-type=%s' % self.disk_type
     ]
     if not util.IsRegion(self.zone):
         create_cmd.append('--availability-zone=%s' % self.zone)
     if self.disk_type == IO1:
         create_cmd.append('--iops=%s' % self.iops)
     stdout, _, _ = vm_util.IssueCommand(create_cmd)
     response = json.loads(stdout)
     self.id = response['VolumeId']
     util.AddDefaultTags(self.id, self.region)
 def _CreateOnDemand(self):
     """Create an OnDemand VM instance."""
     placement = []
     if not util.IsRegion(self.zone):
         placement.append('AvailabilityZone=%s' % self.zone)
     if self.use_dedicated_host:
         placement.append('Tenancy=host,HostId=%s' % self.host.id)
         num_hosts = len(self.host_list)
     elif IsPlacementGroupCompatible(self.machine_type):
         placement.append('GroupName=%s' %
                          self.network.placement_group.name)
     placement = ','.join(placement)
     block_device_map = GetBlockDeviceMap(self.machine_type,
                                          self.boot_disk_size, self.image,
                                          self.region)
     create_cmd = util.AWS_PREFIX + [
         'ec2', 'run-instances',
         '--region=%s' % self.region,
         '--subnet-id=%s' % self.network.subnet.id,
         '--associate-public-ip-address',
         '--client-token=%s' % self.client_token,
         '--image-id=%s' % self.image,
         '--instance-type=%s' % self.machine_type,
         '--key-name=%s' % 'perfkit-key-%s' % FLAGS.run_uri
     ]
     if block_device_map:
         create_cmd.append('--block-device-mappings=%s' % block_device_map)
     if placement:
         create_cmd.append('--placement=%s' % placement)
     if self.user_data:
         create_cmd.append('--user-data=%s' % self.user_data)
     _, stderr, _ = vm_util.IssueCommand(create_cmd)
     if self.use_dedicated_host and 'InsufficientCapacityOnHost' in stderr:
         logging.warning(
             'Creation failed due to insufficient host capacity. A new host will '
             'be created and instance creation will be retried.')
         with self._lock:
             if num_hosts == len(self.host_list):
                 host = AwsDedicatedHost(self.machine_type, self.zone)
                 self.host_list.append(host)
                 host.Create()
             self.host = self.host_list[-1]
         self.client_token = str(uuid.uuid4())
         raise errors.Resource.RetryableCreationError()
     if 'InsufficientInstanceCapacity' in stderr:
         raise errors.Benchmarks.InsufficientCapacityCloudFailure(stderr)
  def _PostCreate(self):
    """Get the instance's data and tag it."""
    describe_cmd = util.AWS_PREFIX + [
        'ec2',
        'describe-instances',
        '--region=%s' % self.region,
        '--instance-ids=%s' % self.id]
    logging.info('Getting instance %s public IP. This will fail until '
                 'a public IP is available, but will be retried.', self.id)
    stdout, _ = util.IssueRetryableCommand(describe_cmd)
    response = json.loads(stdout)
    instance = response['Reservations'][0]['Instances'][0]
    self.ip_address = instance['PublicIpAddress']
    self.internal_ip = instance['PrivateIpAddress']
    if util.IsRegion(self.zone):
      self.zone = str(instance['Placement']['AvailabilityZone'])
    util.AddDefaultTags(self.id, self.region)

    assert self.group_id == instance['SecurityGroups'][0]['GroupId'], (
        self.group_id, instance['SecurityGroups'][0]['GroupId'])
Пример #11
0
 def __init__(self, spec):
     super(EksCluster, self).__init__(spec)
     # EKS requires a region and optionally a list of zones.
     # Interpret the zone as a comma separated list of zones or a region.
     self.zones = sorted(FLAGS.eks_zones) or (self.zone
                                              and self.zone.split(','))
     if not self.zones:
         raise errors.Config.MissingOption(
             'container_cluster.vm_spec.AWS.zone is required.')
     elif len(self.zones) > 1:
         self.region = util.GetRegionFromZone(self.zones[0])
         self.zone = ','.join(self.zones)
     elif util.IsRegion(self.zones[0]):
         self.region = self.zone = self.zones[0]
         self.zones = []
         logging.info("Interpreting zone '%s' as a region", self.zone)
     else:
         raise errors.Config.InvalidValue(
             'container_cluster.vm_spec.AWS.zone must either be a comma separated '
             'list of zones or a region.')
     self.cluster_version = FLAGS.container_cluster_version
Пример #12
0
  def _PostCreate(self):
    """Get the instance's data and tag it."""
    describe_cmd = util.AWS_PREFIX + [
        'ec2',
        'describe-instances',
        '--region=%s' % self.region,
        '--instance-ids=%s' % self.id]
    logging.info('Getting instance %s public IP. This will fail until '
                 'a public IP is available, but will be retried.', self.id)
    stdout, _ = util.IssueRetryableCommand(describe_cmd)
    response = json.loads(stdout)
    instance = response['Reservations'][0]['Instances'][0]
    self.ip_address = instance['PublicIpAddress']
    self.internal_ip = instance['PrivateIpAddress']
    if util.IsRegion(self.zone):
      self.zone = str(instance['Placement']['AvailabilityZone'])

    assert self.group_id == instance['SecurityGroups'][0]['GroupId'], (
        self.group_id, instance['SecurityGroups'][0]['GroupId'])
    if FLAGS.aws_efa:
      self.InstallPackages('curl')
      url = _EFA_URL.format(version=FLAGS.aws_efa_version)
      self.RemoteCommand(
          _EFA_INSTALL_CMD.format(url=url, tarfile=posixpath.basename(url)))
Пример #13
0
 def testRegion(self):
     self.assertTrue(util.IsRegion('eu-central-1'))
Пример #14
0
 def testZone(self):
     self.assertFalse(util.IsRegion('us-east-1a'))
Пример #15
0
 def testBadFormat(self):
     with self.assertRaises(ValueError):
         util.IsRegion('us-east')
Пример #16
0
  def _Create(self):
    """Create a VM instance."""
    placement = []
    if not util.IsRegion(self.zone):
      placement.append('AvailabilityZone=%s' % self.zone)
    if self.use_dedicated_host:
      placement.append('Tenancy=host,HostId=%s' % self.host.id)
      num_hosts = len(self.host_list)
    elif self.placement_group:
      if IsPlacementGroupCompatible(self.machine_type):
        placement.append('GroupName=%s' % self.placement_group.name)
      else:
        logging.warn(
            'VM not placed in Placement Group. VM Type %s not supported',
            self.machine_type)
    placement = ','.join(placement)
    block_device_map = GetBlockDeviceMap(self.machine_type,
                                         self.boot_disk_size,
                                         self.image,
                                         self.region)
    tags = {}
    tags.update(self.vm_metadata)
    tags.update(util.MakeDefaultTags())
    create_cmd = util.AWS_PREFIX + [
        'ec2',
        'run-instances',
        '--region=%s' % self.region,
        '--subnet-id=%s' % self.network.subnet.id,
        '--client-token=%s' % self.client_token,
        '--image-id=%s' % self.image,
        '--instance-type=%s' % self.machine_type,
        '--key-name=%s' % AwsKeyFileManager.GetKeyNameForRun(),
        '--tag-specifications=%s' %
        util.FormatTagSpecifications('instance', tags)]
    if FLAGS.aws_efa:
      create_cmd.extend([
          '--network-interfaces',
          ','.join(['%s=%s' % item for item in sorted(_EFA_PARAMS.items())])
      ])
    else:
      create_cmd.append('--associate-public-ip-address')
    if block_device_map:
      create_cmd.append('--block-device-mappings=%s' % block_device_map)
    if placement:
      create_cmd.append('--placement=%s' % placement)
    if FLAGS.aws_credit_specification:
      create_cmd.append('--credit-specification=%s' %
                        FLAGS.aws_credit_specification)
    if self.user_data:
      create_cmd.append('--user-data=%s' % self.user_data)
    if self.capacity_reservation_id:
      create_cmd.append(
          '--capacity-reservation-specification=CapacityReservationTarget='
          '{CapacityReservationId=%s}' % self.capacity_reservation_id)
    if self.use_spot_instance:
      instance_market_options = collections.OrderedDict()
      spot_options = collections.OrderedDict()
      spot_options['SpotInstanceType'] = 'one-time'
      spot_options['InstanceInterruptionBehavior'] = 'terminate'
      if self.spot_price:
        spot_options['MaxPrice'] = str(self.spot_price)
      instance_market_options['MarketType'] = 'spot'
      instance_market_options['SpotOptions'] = spot_options
      create_cmd.append(
          '--instance-market-options=%s' % json.dumps(instance_market_options))
    _, stderr, retcode = vm_util.IssueCommand(create_cmd,
                                              raise_on_failure=False)

    machine_type_prefix = self.machine_type.split('.')[0]
    host_arch = _MACHINE_TYPE_PREFIX_TO_HOST_ARCH.get(machine_type_prefix)
    if host_arch:
      self.host_arch = host_arch

    if self.use_dedicated_host and 'InsufficientCapacityOnHost' in stderr:
      if self.num_vms_per_host:
        raise errors.Resource.CreationError(
            'Failed to create host: %d vms of type %s per host exceeds '
            'memory capacity limits of the host' %
            (self.num_vms_per_host, self.machine_type))
      else:
        logging.warning(
            'Creation failed due to insufficient host capacity. A new host will '
            'be created and instance creation will be retried.')
        with self._lock:
          if num_hosts == len(self.host_list):
            host = AwsDedicatedHost(self.machine_type, self.zone)
            self.host_list.append(host)
            host.Create()
          self.host = self.host_list[-1]
        self.client_token = str(uuid.uuid4())
        raise errors.Resource.RetryableCreationError()
    if 'InsufficientInstanceCapacity' in stderr:
      if self.use_spot_instance:
        self.spot_status_code = 'InsufficientSpotInstanceCapacity'
        self.early_termination = True
      raise errors.Benchmarks.InsufficientCapacityCloudFailure(stderr)
    if 'SpotMaxPriceTooLow' in stderr:
      self.spot_status_code = 'SpotMaxPriceTooLow'
      self.early_termination = True
      raise errors.Resource.CreationError(stderr)
    if 'InstanceLimitExceeded' in stderr:
      raise errors.Benchmarks.QuotaFailure(stderr)
    if retcode:
      raise errors.Resource.CreationError(
          'Failed to create VM: %s return code: %s' % (retcode, stderr))
 def _Create(self):
   """Create a VM instance."""
   placement = []
   if not util.IsRegion(self.zone):
     placement.append('AvailabilityZone=%s' % self.zone)
   if self.use_dedicated_host:
     placement.append('Tenancy=host,HostId=%s' % self.host.id)
     num_hosts = len(self.host_list)
   elif IsPlacementGroupCompatible(self.machine_type):
     placement.append('GroupName=%s' % self.network.placement_group.name)
   placement = ','.join(placement)
   block_device_map = GetBlockDeviceMap(self.machine_type,
                                        self.boot_disk_size,
                                        self.image,
                                        self.region)
   create_cmd = util.AWS_PREFIX + [
       'ec2',
       'run-instances',
       '--region=%s' % self.region,
       '--subnet-id=%s' % self.network.subnet.id,
       '--associate-public-ip-address',
       '--client-token=%s' % self.client_token,
       '--image-id=%s' % self.image,
       '--instance-type=%s' % self.machine_type,
       '--key-name=%s' % 'perfkit-key-%s' % FLAGS.run_uri]
   if block_device_map:
     create_cmd.append('--block-device-mappings=%s' % block_device_map)
   if placement:
     create_cmd.append('--placement=%s' % placement)
   if self.user_data:
     create_cmd.append('--user-data=%s' % self.user_data)
   if self.use_spot_instance:
     instance_market_options = OrderedDict()
     spot_options = OrderedDict()
     spot_options['SpotInstanceType'] = 'one-time'
     spot_options['InstanceInterruptionBehavior'] = 'terminate'
     if self.spot_price:
       spot_options['MaxPrice'] = str(self.spot_price)
     instance_market_options['MarketType'] = 'spot'
     instance_market_options['SpotOptions'] = spot_options
     create_cmd.append(
         '--instance-market-options=%s' % json.dumps(instance_market_options))
   _, stderr, retcode = vm_util.IssueCommand(create_cmd)
   if self.use_dedicated_host and 'InsufficientCapacityOnHost' in stderr:
     logging.warning(
         'Creation failed due to insufficient host capacity. A new host will '
         'be created and instance creation will be retried.')
     with self._lock:
       if num_hosts == len(self.host_list):
         host = AwsDedicatedHost(self.machine_type, self.zone)
         self.host_list.append(host)
         host.Create()
       self.host = self.host_list[-1]
     self.client_token = str(uuid.uuid4())
     raise errors.Resource.RetryableCreationError()
   if 'InsufficientInstanceCapacity' in stderr:
     if self.use_spot_instance:
       self.spot_status_code = 'InsufficientSpotInstanceCapacity'
       self.early_termination = True
     raise errors.Benchmarks.InsufficientCapacityCloudFailure(stderr)
   if 'SpotMaxPriceTooLow' in stderr:
     self.spot_status_code = 'SpotMaxPriceTooLow'
     self.early_termination = True
     raise errors.Resource.CreationError(stderr)
   if retcode:
     raise errors.Resource.CreationError(
         '%s return code: %s' % (retcode, stderr))
  def _Create(self):
    """Creates the AWS CapacaityReservation.

    A reservation will be created given the VM shape in self.vm_groups.
    Count is determined by the number of VMs in said group. The reservation
    will have a lifetime determined by the general PKB concept of
    timeout_minutes. If the reservation exceeds this timeout, AWS will
    cancel it automatically. The VMs in the reservation will not be deleted.
    Note that an empty capacity reservation will encur costs for the
    VM shape / count, even if no VMs are using it.

    After the reservation is created, this method updates all the VMs
    in self.vm_groups by setting the capacity_reservation_id, as well
    as the zone attributes on the VM, and the VM's network instance.

    Raises:
      UnsupportedOsTypeError: If creating a capacity reservation for the
        given os type is not supported.
      CreationError: If a capacity reservation cannot be created in the
        region (typically indicates a stockout).
    """
    if self.os_type in os_types.LINUX_OS_TYPES:
      instance_platform = 'Linux/UNIX'
    elif self.os_type in os_types.WINDOWS_OS_TYPES:
      instance_platform = 'Windows'
    else:
      raise UnsupportedOsTypeError(
          'Unsupported os_type for AWS CapacityReservation: %s.'
          % self.os_type)

    # If the user did not specify an AZ, we need to try to create the
    # CapacityReservation in a specifc AZ until it succeeds.
    # Then update the zone attribute on all the VMs in the group,
    # as well as the zone attribute on the VMs' network instance.
    if util.IsRegion(self.zone_or_region):
      zones_to_try = util.GetZonesInRegion(self.region)
    else:
      zones_to_try = [self.zone_or_region]

    end_date = (
        datetime.datetime.utcnow() +
        datetime.timedelta(minutes=FLAGS.timeout_minutes))
    for zone in zones_to_try:
      cmd = util.AWS_PREFIX + [
          'ec2',
          'create-capacity-reservation',
          '--instance-type=%s' % self.machine_type,
          '--instance-platform=%s' % instance_platform,
          '--availability-zone=%s' % zone,
          '--instance-count=%s' % self.vm_count,
          '--instance-match-criteria=targeted',
          '--region=%s' % self.region,
          '--end-date-type=limited',
          '--end-date=%s' % end_date,
      ]
      stdout, stderr, retcode = vm_util.IssueCommand(cmd)
      if retcode:
        logging.info('Unable to create CapacityReservation in %s. '
                     'This may be retried. Details: %s', zone, stderr)
        continue
      json_output = json.loads(stdout)
      self.capacity_reservation_id = (
          json_output['CapacityReservation']['CapacityReservationId'])
      self._UpdateVmsInGroup(self.capacity_reservation_id, zone)
      return
    raise CreationError('Unable to create CapacityReservation in any of the '
                        'following zones: %s.' % zones_to_try)
    def _CreateSpot(self):
        """Create a Spot VM instance."""
        placement = OrderedDict()
        if not util.IsRegion(self.zone):
            placement['AvailabilityZone'] = self.zone
        if self.use_dedicated_host:
            raise errors.Resource.CreationError(
                'Tenancy=host is not supported for Spot Instances')
        elif IsPlacementGroupCompatible(self.machine_type):
            placement['GroupName'] = self.network.placement_group.name
        block_device_map = GetBlockDeviceMap(self.machine_type,
                                             self.boot_disk_size, self.image,
                                             self.region)
        network_interface = [
            OrderedDict([('DeviceIndex', 0),
                         ('AssociatePublicIpAddress', True),
                         ('SubnetId', self.network.subnet.id)])
        ]
        launch_specification = OrderedDict([
            ('ImageId', self.image), ('InstanceType', self.machine_type),
            ('KeyName', 'perfkit-key-%s' % FLAGS.run_uri),
            ('Placement', placement)
        ])
        if block_device_map:
            launch_specification['BlockDeviceMappings'] = json.loads(
                block_device_map, object_pairs_hook=collections.OrderedDict)
        launch_specification['NetworkInterfaces'] = network_interface
        create_cmd = util.AWS_PREFIX + [
            'ec2', 'request-spot-instances',
            '--region=%s' % self.region,
            '--spot-price=%s' % self.spot_price,
            '--client-token=%s' % self.client_token,
            '--launch-specification=%s' %
            json.dumps(launch_specification, separators=(',', ':'))
        ]
        stdout, _, _ = vm_util.IssueCommand(create_cmd)
        create_response = json.loads(stdout)
        self.spot_instance_request_id = (
            create_response['SpotInstanceRequests'][0]['SpotInstanceRequestId']
        )

        util.AddDefaultTags(self.spot_instance_request_id, self.region)

        while True:
            describe_sir_cmd = util.AWS_PREFIX + [
                '--region=%s' % self.region, 'ec2',
                'describe-spot-instance-requests',
                '--spot-instance-request-ids=%s' %
                self.spot_instance_request_id
            ]
            stdout, _, _ = vm_util.IssueCommand(describe_sir_cmd)

            sir_response = json.loads(stdout)['SpotInstanceRequests']
            assert len(
                sir_response) == 1, 'Expected exactly 1 SpotInstanceRequest'

            status_code = sir_response[0]['Status']['Code']

            if (status_code in SPOT_INSTANCE_REQUEST_HOLDING_STATUSES
                    or status_code in SPOT_INSTANCE_REQUEST_TERMINAL_STATUSES):
                message = sir_response[0]['Status']['Message']
                raise errors.Resource.CreationError(message)
            elif status_code == 'fulfilled':
                self.id = sir_response[0]['InstanceId']
                break

            time.sleep(2)