Create a single VM from a data dict

def create(vm_):
    '''
    Create a single VM from a data dict
    '''
    try:
        # Check for required profile parameters before sending any API calls.
        if vm_['profile'] and config.is_profile_configured(__opts__,
                                                           __active_provider_name__ or 'azure',
                                                           vm_['profile'],
                                                           vm_=vm_) is False:
            return False
    except AttributeError:
        pass

    __utils__['cloud.fire_event'](
        'event',
        'starting create',
        'salt/cloud/{0}/creating'.format(vm_['name']),
        args=__utils__['cloud.filter_event']('creating', vm_, ['name', 'profile', 'provider', 'driver']),
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )

    log.info('Creating Cloud VM %s', vm_['name'])
    conn = get_conn()

    label = vm_.get('label', vm_['name'])
    service_name = vm_.get('service_name', vm_['name'])
    service_kwargs = {
        'service_name': service_name,
        'label': label,
        'description': vm_.get('desc', vm_['name']),
    }

    loc_error = False
    if 'location' in vm_:
        if 'affinity_group' in vm_:
            loc_error = True
        else:
            service_kwargs['location'] = vm_['location']
    elif 'affinity_group' in vm_:
        service_kwargs['affinity_group'] = vm_['affinity_group']
    else:
        loc_error = True

    if loc_error:
        raise SaltCloudSystemExit(
            'Either a location or affinity group must be specified, but not both'
        )

    ssh_port = config.get_cloud_config_value('port', vm_, __opts__,
                                             default=22, search_global=True)

    ssh_endpoint = azure.servicemanagement.ConfigurationSetInputEndpoint(
        name='SSH',
        protocol='TCP',
        port=ssh_port,
        local_port=22,
    )

    network_config = azure.servicemanagement.ConfigurationSet()
    network_config.input_endpoints.input_endpoints.append(ssh_endpoint)
    network_config.configuration_set_type = 'NetworkConfiguration'

    if 'win_username' in vm_:
        system_config = azure.servicemanagement.WindowsConfigurationSet(
            computer_name=vm_['name'],
            admin_username=vm_['win_username'],
            admin_password=vm_['win_password'],
        )

        smb_port = '445'
        if 'smb_port' in vm_:
            smb_port = vm_['smb_port']

        smb_endpoint = azure.servicemanagement.ConfigurationSetInputEndpoint(
            name='SMB',
            protocol='TCP',
            port=smb_port,
            local_port=smb_port,
        )

        network_config.input_endpoints.input_endpoints.append(smb_endpoint)

        # Domain and WinRM configuration not yet supported by Salt Cloud
        system_config.domain_join = None
        system_config.win_rm = None

    else:
        system_config = azure.servicemanagement.LinuxConfigurationSet(
            host_name=vm_['name'],
            user_name=vm_['ssh_username'],
            user_password=vm_['ssh_password'],
            disable_ssh_password_authentication=False,
        )

    # TODO: Might need to create a storage account
    media_link = vm_['media_link']
    # TODO: Probably better to use more than just the name in the media_link
    media_link += '/{0}.vhd'.format(vm_['name'])
    os_hd = azure.servicemanagement.OSVirtualHardDisk(vm_['image'], media_link)

    vm_kwargs = {
        'service_name': service_name,
        'deployment_name': service_name,
        'deployment_slot': vm_['slot'],
        'label': label,
        'role_name': vm_['name'],
        'system_config': system_config,
        'os_virtual_hard_disk': os_hd,
        'role_size': vm_['size'],
        'network_config': network_config,
    }

    if 'virtual_network_name' in vm_:
        vm_kwargs['virtual_network_name'] = vm_['virtual_network_name']
        if 'subnet_name' in vm_:
            network_config.subnet_names.append(vm_['subnet_name'])

    log.debug('vm_kwargs: %s', vm_kwargs)

    event_kwargs = {'service_kwargs': service_kwargs.copy(),
                    'vm_kwargs': vm_kwargs.copy()}
    del event_kwargs['vm_kwargs']['system_config']
    del event_kwargs['vm_kwargs']['os_virtual_hard_disk']
    del event_kwargs['vm_kwargs']['network_config']
    __utils__['cloud.fire_event'](
        'event',
        'requesting instance',
        'salt/cloud/{0}/requesting'.format(vm_['name']),
        args=__utils__['cloud.filter_event']('requesting', event_kwargs, list(event_kwargs)),
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )
    log.debug('vm_kwargs: %s', vm_kwargs)

    # Azure lets you open winrm on a new VM
    # Can open up specific ports in Azure; but not on Windows
    try:
        conn.create_hosted_service(**service_kwargs)
    except AzureConflictHttpError:
        log.debug('Cloud service already exists')
    except Exception as exc:
        error = 'The hosted service name is invalid.'
        if error in six.text_type(exc):
            log.error(
                'Error creating %s on Azure.\n\n'
                'The hosted service name is invalid. The name can contain '
                'only letters, numbers, and hyphens. The name must start with '
                'a letter and must end with a letter or a number.',
                vm_['name'],
                # Show the traceback if the debug logging level is enabled
                exc_info_on_loglevel=logging.DEBUG
            )
        else:
            log.error(
                'Error creating %s on Azure\n\n'
                'The following exception was thrown 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
            )
        return False
    try:
        result = conn.create_virtual_machine_deployment(**vm_kwargs)
        log.debug('Request ID for machine: %s', result.request_id)
        _wait_for_async(conn, result.request_id)
    except AzureConflictHttpError:
        log.debug('Conflict error. The deployment may already exist, trying add_role')
        # Deleting two useless keywords
        del vm_kwargs['deployment_slot']
        del vm_kwargs['label']
        del vm_kwargs['virtual_network_name']
        result = conn.add_role(**vm_kwargs)
        _wait_for_async(conn, result.request_id)
    except Exception as exc:
        error = 'The hosted service name is invalid.'
        if error in six.text_type(exc):
            log.error(
                'Error creating %s on Azure.\n\n'
                'The VM name is invalid. The name can contain '
                'only letters, numbers, and hyphens. The name must start with '
                'a letter and must end with a letter or a number.',
                vm_['name'],
                # Show the traceback if the debug logging level is enabled
                exc_info_on_loglevel=logging.DEBUG
            )
        else:
            log.error(
                'Error creating %s on Azure.\n\n'
                'The Virtual Machine could not be created. If you '
                'are using an already existing Cloud Service, '
                'make sure you set up the `port` variable corresponding '
                'to the SSH port exists and that the port number is not '
                'already in use.\nThe following exception was thrown 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
            )
        return False

    def wait_for_hostname():
        '''
        Wait for the IP address to become available
        '''
        try:
            conn.get_role(service_name, service_name, vm_['name'])
            data = show_instance(vm_['name'], call='action')
            if 'url' in data and data['url'] != six.text_type(''):
                return data['url']
        except AzureMissingResourceHttpError:
            pass
        time.sleep(1)
        return False

    hostname = salt.utils.cloud.wait_for_fun(
        wait_for_hostname,
        timeout=config.get_cloud_config_value(
            'wait_for_fun_timeout', vm_, __opts__, default=15 * 60),
    )

    if not hostname:
        log.error('Failed to get a value for the hostname.')
        return False

    vm_['ssh_host'] = hostname.replace('http://', '').replace('/', '')
    vm_['password'] = config.get_cloud_config_value(
        'ssh_password', vm_, __opts__
    )
    ret = __utils__['cloud.bootstrap'](vm_, __opts__)

    # Attaching volumes
    volumes = config.get_cloud_config_value(
        'volumes', vm_, __opts__, search_global=True
    )
    if volumes:
        __utils__['cloud.fire_event'](
            'event',
            'attaching volumes',
            'salt/cloud/{0}/attaching_volumes'.format(vm_['name']),
            args=__utils__['cloud.filter_event']('attaching_volumes', vm_, ['volumes']),
            sock_dir=__opts__['sock_dir'],
            transport=__opts__['transport']
        )

        log.info('Create and attach volumes to node %s', vm_['name'])
        created = create_attach_volumes(
            vm_['name'],
            {
                'volumes': volumes,
                'service_name': service_name,
                'deployment_name': vm_['name'],
                'media_link': media_link,
                'role_name': vm_['name'],
                'del_all_vols_on_destroy': vm_.get('set_del_all_vols_on_destroy', False)
            },
            call='action'
        )
        ret['Attached Volumes'] = created

    data = show_instance(vm_['name'], call='action')
    log.info('Created Cloud VM \'%s\'', vm_)
    log.debug('\'%s\' VM creation details:\n%s', vm_['name'], pprint.pformat(data))

    ret.update(data)

    __utils__['cloud.fire_event'](
        'event',
        'created instance',
        'salt/cloud/{0}/created'.format(vm_['name']),
        args=__utils__['cloud.filter_event']('created', vm_, ['name', 'profile', 'provider', 'driver']),
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )

    return ret