docs: def request_instance(vm_=None, call=None):
      '''
      Put together all of the information necessary to request an instance on EC2,
      and then fire off the request the instance.
  
      Returns data about the instance
      '''
      if call == 'function':
          # Technically this function may be called other ways too, but it
          # definitely cannot be called with --function.
          raise SaltCloudSystemExit(
              'The request_instance action must be called with -a or --action.'
          )
  
      location = vm_.get('location', get_location(vm_))
  
      # do we launch a regular vm or a spot instance?
      # see http://goo.gl/hYZ13f for more information on EC2 API
      spot_config = get_spot_config(vm_)
      if spot_config is not None:
          if 'spot_price' not in spot_config:
              raise SaltCloudSystemExit(
                  'Spot instance config for {0} requires a spot_price '
                  'attribute.'.format(vm_['name'])
              )
  
          params = {'Action': 'RequestSpotInstances',
                    'InstanceCount': '1',
                    'Type': spot_config['type']
                    if 'type' in spot_config else 'one-time',
                    'SpotPrice': spot_config['spot_price']}
  
          # All of the necessary launch parameters for a VM when using
          # spot instances are the same except for the prefix below
          # being tacked on.
          spot_prefix = 'LaunchSpecification.'
  
      # regular EC2 instance
      else:
          # WARNING! EXPERIMENTAL!
          # This allows more than one instance to be spun up in a single call.
          # The first instance will be called by the name provided, but all other
          # instances will be nameless (or more specifically, they will use the
          # InstanceId as the name). This interface is expected to change, so
          # use at your own risk.
          min_instance = config.get_cloud_config_value(
              'min_instance', vm_, __opts__, search_global=False, default=1
          )
          max_instance = config.get_cloud_config_value(
              'max_instance', vm_, __opts__, search_global=False, default=1
          )
          params = {'Action': 'RunInstances',
                    'MinCount': min_instance,
                    'MaxCount': max_instance}
  
          # Normal instances should have no prefix.
          spot_prefix = ''
  
      image_id = get_imageid(vm_)
      params[spot_prefix + 'ImageId'] = image_id
  
      userdata = None
      userdata_file = config.get_cloud_config_value(
          'userdata_file', vm_, __opts__, search_global=False, default=None
      )
      if userdata_file is None:
          userdata = config.get_cloud_config_value(
              'userdata', vm_, __opts__, search_global=False, default=None
          )
      else:
          log.trace('userdata_file: %s', userdata_file)
          if os.path.exists(userdata_file):
              with salt.utils.files.fopen(userdata_file, 'r') as fh_:
                  userdata = salt.utils.stringutils.to_unicode(fh_.read())
  
      userdata = salt.utils.cloud.userdata_template(__opts__, vm_, userdata)
  
      if userdata is not None:
          try:
              params[spot_prefix + 'UserData'] = base64.b64encode(
                  salt.utils.stringutils.to_bytes(userdata)
              )
          except Exception as exc:
              log.exception('Failed to encode userdata: %s', exc)
  
      vm_size = config.get_cloud_config_value(
          'size', vm_, __opts__, search_global=False
      )
      params[spot_prefix + 'InstanceType'] = vm_size
  
      ex_keyname = keyname(vm_)
      if ex_keyname:
          params[spot_prefix + 'KeyName'] = ex_keyname
  
      ex_securitygroup = securitygroup(vm_)
      if ex_securitygroup:
          if not isinstance(ex_securitygroup, list):
              params[spot_prefix + 'SecurityGroup.1'] = ex_securitygroup
          else:
              for counter, sg_ in enumerate(ex_securitygroup):
                  params[spot_prefix + 'SecurityGroup.{0}'.format(counter)] = sg_
  
      ex_iam_profile = iam_profile(vm_)
      if ex_iam_profile:
          try:
              if ex_iam_profile.startswith('arn:aws:iam:'):
                  params[
                      spot_prefix + 'IamInstanceProfile.Arn'
                  ] = ex_iam_profile
              else:
                  params[
                      spot_prefix + 'IamInstanceProfile.Name'
                  ] = ex_iam_profile
          except AttributeError:
              raise SaltCloudConfigError(
                  '\'iam_profile\' should be a string value.'
              )
  
      az_ = get_availability_zone(vm_)
      if az_ is not None:
          params[spot_prefix + 'Placement.AvailabilityZone'] = az_
  
      tenancy_ = get_tenancy(vm_)
      if tenancy_ is not None:
          if spot_config is not None:
              raise SaltCloudConfigError(
                  'Spot instance config for {0} does not support '
                  'specifying tenancy.'.format(vm_['name'])
              )
          params['Placement.Tenancy'] = tenancy_
  
      subnetid_ = get_subnetid(vm_)
      if subnetid_ is not None:
          params[spot_prefix + 'SubnetId'] = subnetid_
  
      ex_securitygroupid = securitygroupid(vm_)
      if ex_securitygroupid:
          if not isinstance(ex_securitygroupid, list):
              params[spot_prefix + 'SecurityGroupId.1'] = ex_securitygroupid
          else:
              for counter, sg_ in enumerate(ex_securitygroupid):
                  params[
                      spot_prefix + 'SecurityGroupId.{0}'.format(counter)
                  ] = sg_
  
      placementgroup_ = get_placementgroup(vm_)
      if placementgroup_ is not None:
          params[spot_prefix + 'Placement.GroupName'] = placementgroup_
  
      blockdevicemappings_holder = block_device_mappings(vm_)
      if blockdevicemappings_holder:
          for _bd in blockdevicemappings_holder:
              if 'tag' in _bd:
                  _bd.pop('tag')
  
      ex_blockdevicemappings = blockdevicemappings_holder
      if ex_blockdevicemappings:
          params.update(_param_from_config(spot_prefix + 'BlockDeviceMapping',
                        ex_blockdevicemappings))
  
      network_interfaces = config.get_cloud_config_value(
          'network_interfaces',
          vm_,
          __opts__,
          search_global=False
      )
  
      if network_interfaces:
          eni_devices = []
          for interface in network_interfaces:
              log.debug('Create network interface: %s', interface)
              _new_eni = _create_eni_if_necessary(interface, vm_)
              eni_devices.append(_new_eni)
          params.update(_param_from_config(spot_prefix + 'NetworkInterface',
                                           eni_devices))
  
      set_ebs_optimized = config.get_cloud_config_value(
          'ebs_optimized', vm_, __opts__, search_global=False
      )
  
      if set_ebs_optimized is not None:
          if not isinstance(set_ebs_optimized, bool):
              raise SaltCloudConfigError(
                  '\'ebs_optimized\' should be a boolean value.'
              )
          params[spot_prefix + 'EbsOptimized'] = set_ebs_optimized
  
      set_del_root_vol_on_destroy = config.get_cloud_config_value(
          'del_root_vol_on_destroy', vm_, __opts__, search_global=False
      )
  
      set_termination_protection = config.get_cloud_config_value(
          'termination_protection', vm_, __opts__, search_global=False
      )
  
      if set_termination_protection is not None:
          if not isinstance(set_termination_protection, bool):
              raise SaltCloudConfigError(
                  '\'termination_protection\' should be a boolean value.'
              )
          params.update(_param_from_config(spot_prefix + 'DisableApiTermination',
                                           set_termination_protection))
  
      if set_del_root_vol_on_destroy and not isinstance(set_del_root_vol_on_destroy, bool):
          raise SaltCloudConfigError(
              '\'del_root_vol_on_destroy\' should be a boolean value.'
          )
  
      vm_['set_del_root_vol_on_destroy'] = set_del_root_vol_on_destroy
  
      if set_del_root_vol_on_destroy:
          # first make sure to look up the root device name
          # as Ubuntu and CentOS (and most likely other OSs)
          # use different device identifiers
  
          log.info('Attempting to look up root device name for image id %s on '
                   'VM %s', image_id, vm_['name'])
  
          rd_params = {
              'Action': 'DescribeImages',
              'ImageId.1': image_id
          }
          try:
              rd_data = aws.query(rd_params,
                                  location=get_location(vm_),
                                  provider=get_provider(),
                                  opts=__opts__,
                                  sigver='4')
              if 'error' in rd_data:
                  return rd_data['error']
              log.debug('EC2 Response: \'%s\'', rd_data)
          except Exception as exc:
              log.error(
                  'Error getting root device name for image id %s for '
                  'VM %s: \n%s', image_id, vm_['name'], exc,
                  # Show the traceback if the debug logging level is enabled
                  exc_info_on_loglevel=logging.DEBUG
              )
              raise
  
          # make sure we have a response
          if not rd_data:
              err_msg = 'There was an error querying EC2 for the root device ' \
                        'of image id {0}. Empty response.'.format(image_id)
              raise SaltCloudSystemExit(err_msg)
  
          # pull the root device name from the result and use it when
          # launching the new VM
          rd_name = None
          rd_type = None
          if 'blockDeviceMapping' in rd_data[0]:
              # Some ami instances do not have a root volume. Ignore such cases
              if rd_data[0]['blockDeviceMapping'] is not None:
                  item = rd_data[0]['blockDeviceMapping']['item']
                  if isinstance(item, list):
                      item = item[0]
                  rd_name = item['deviceName']
                  # Grab the volume type
                  rd_type = item['ebs'].get('volumeType', None)
  
              log.info('Found root device name: %s', rd_name)
  
          if rd_name is not None:
              if ex_blockdevicemappings:
                  dev_list = [
                      dev['DeviceName'] for dev in ex_blockdevicemappings
                  ]
              else:
                  dev_list = []
  
              if rd_name in dev_list:
                  # Device already listed, just grab the index
                  dev_index = dev_list.index(rd_name)
              else:
                  dev_index = len(dev_list)
                  # Add the device name in since it wasn't already there
                  params[
                      '{0}BlockDeviceMapping.{1}.DeviceName'.format(
                          spot_prefix, dev_index
                      )
                  ] = rd_name
  
              # Set the termination value
              termination_key = '{0}BlockDeviceMapping.{1}.Ebs.DeleteOnTermination'.format(spot_prefix, dev_index)
              params[termination_key] = six.text_type(set_del_root_vol_on_destroy).lower()
  
              # Use default volume type if not specified
              if ex_blockdevicemappings and dev_index < len(ex_blockdevicemappings) and \
                     'Ebs.VolumeType' not in ex_blockdevicemappings[dev_index]:
                  type_key = '{0}BlockDeviceMapping.{1}.Ebs.VolumeType'.format(spot_prefix, dev_index)
                  params[type_key] = rd_type
  
      set_del_all_vols_on_destroy = config.get_cloud_config_value(
          'del_all_vols_on_destroy', vm_, __opts__, search_global=False, default=False
      )
  
      if set_del_all_vols_on_destroy and not isinstance(set_del_all_vols_on_destroy, bool):
          raise SaltCloudConfigError(
              '\'del_all_vols_on_destroy\' should be a boolean value.'
          )
  
      __utils__['cloud.fire_event'](
          'event',
          'requesting instance',
          'salt/cloud/{0}/requesting'.format(vm_['name']),
          args={
              'kwargs': __utils__['cloud.filter_event'](
                  'requesting', params, list(params)
              ),
              'location': location,
          },
          sock_dir=__opts__['sock_dir'],
          transport=__opts__['transport']
      )
  
      provider = get_provider(vm_)
  
      try:
          data = aws.query(params,
                           'instancesSet',
                           location=location,
                           provider=provider,
                           opts=__opts__,
                           sigver='4')
          if 'error' in data:
              return data['error']
      except Exception as exc:
          log.error(
              'Error creating %s on EC2 when trying to run the initial '
              'deployment: \n%s', vm_['name'], exc,
              # Show the traceback if the debug logging level is enabled
              exc_info_on_loglevel=logging.DEBUG
          )
          raise
  
      # if we're using spot instances, we need to wait for the spot request
      # to become active before we continue
      if spot_config:
          sir_id = data[0]['spotInstanceRequestId']
  
          vm_['spotRequestId'] = sir_id
  
          def __query_spot_instance_request(sir_id, location):
              params = {'Action': 'DescribeSpotInstanceRequests',
                        'SpotInstanceRequestId.1': sir_id}
              data = aws.query(params,
                               location=location,
                               provider=provider,
                               opts=__opts__,
                               sigver='4')
              if not data:
                  log.error(
                      'There was an error while querying EC2. Empty response'
                  )
                  # Trigger a failure in the wait for spot instance method
                  return False
  
              if isinstance(data, dict) and 'error' in data:
                  log.warning('There was an error in the query. %s', data['error'])
                  # Trigger a failure in the wait for spot instance method
                  return False
  
              log.debug('Returned query data: %s', data)
  
              state = data[0].get('state')
  
              if state == 'active':
                  return data
  
              if state == 'open':
                  # Still waiting for an active state
                  log.info('Spot instance status: %s', data[0]['status']['message'])
                  return None
  
              if state in ['cancelled', 'failed', 'closed']:
                  # Request will never be active, fail
                  log.error('Spot instance request resulted in state \'{0}\'. '
                            'Nothing else we can do here.')
                  return False
  
          __utils__['cloud.fire_event'](
              'event',
              'waiting for spot instance',
              'salt/cloud/{0}/waiting_for_spot'.format(vm_['name']),
              sock_dir=__opts__['sock_dir'],
              transport=__opts__['transport']
          )
  
          try:
              data = _wait_for_spot_instance(
                  __query_spot_instance_request,
                  update_args=(sir_id, location),
                  timeout=config.get_cloud_config_value(
                      'wait_for_spot_timeout', vm_, __opts__, default=10 * 60),
                  interval=config.get_cloud_config_value(
                      'wait_for_spot_interval', vm_, __opts__, default=30),
                  interval_multiplier=config.get_cloud_config_value(
                      'wait_for_spot_interval_multiplier',
                      vm_,
                      __opts__,
                      default=1),
                  max_failures=config.get_cloud_config_value(
                      'wait_for_spot_max_failures',
                      vm_,
                      __opts__,
                      default=10),
              )
              log.debug('wait_for_spot_instance data %s', data)
  
          except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc:
              try:
                  # Cancel the existing spot instance request
                  params = {'Action': 'CancelSpotInstanceRequests',
                            'SpotInstanceRequestId.1': sir_id}
                  data = aws.query(params,
                                   location=location,
                                   provider=provider,
                                   opts=__opts__,
                                   sigver='4')
  
                  log.debug('Canceled spot instance request %s. Data '
                            'returned: %s', sir_id, data)
  
              except SaltCloudSystemExit:
                  pass
              finally:
                  raise SaltCloudSystemExit(six.text_type(exc))
  
      return data, vm_