Configures licenses on the cluster entity

    Checks if each license exists on the server:
        - if it doesn't, it creates it
    Check if license is assigned to the cluster:
        - if it's not assigned to the cluster:
            - assign it to the cluster if there is space
            - error if there's no space
        - if it's assigned to the cluster nothing needs to be done

def licenses_configured(name, licenses=None):
    '''
    Configures licenses on the cluster entity

    Checks if each license exists on the server:
        - if it doesn't, it creates it
    Check if license is assigned to the cluster:
        - if it's not assigned to the cluster:
            - assign it to the cluster if there is space
            - error if there's no space
        - if it's assigned to the cluster nothing needs to be done
    '''
    ret = {'name': name,
           'changes': {},
           'result': None,
           'comment': 'Default'}
    if not licenses:
        raise salt.exceptions.ArgumentValueError('No licenses provided')
    cluster_name, datacenter_name = \
            __salt__['esxcluster.get_details']()['cluster'], \
            __salt__['esxcluster.get_details']()['datacenter']
    display_name = '{0}/{1}'.format(datacenter_name, cluster_name)
    log.info('Running licenses configured for \'%s\'', display_name)
    log.trace('licenses = %s', licenses)
    entity = {'type': 'cluster',
              'datacenter': datacenter_name,
              'cluster': cluster_name}
    log.trace('entity = %s', entity)

    comments = []
    changes = {}
    old_licenses = []
    new_licenses = []
    has_errors = False
    needs_changes = False
    try:
        # Validate licenses
        log.trace('Validating licenses')
        schema = LicenseSchema.serialize()
        try:
            jsonschema.validate({'licenses': licenses}, schema)
        except jsonschema.exceptions.ValidationError as exc:
            raise salt.exceptions.InvalidLicenseError(exc)

        si = __salt__['vsphere.get_service_instance_via_proxy']()
        # Retrieve licenses
        existing_licenses = __salt__['vsphere.list_licenses'](
            service_instance=si)
        remaining_licenses = existing_licenses[:]
        # Cycle through licenses
        for license_name, license in licenses.items():
            # Check if license already exists
            filtered_licenses = [l for l in existing_licenses
                                 if l['key'] == license]
            # TODO Update license description - not of interest right now
            if not filtered_licenses:
                # License doesn't exist - add and assign to cluster
                needs_changes = True
                if __opts__['test']:
                    # If it doesn't exist it clearly needs to be assigned as
                    # well so we can stop the check here
                    comments.append('State {0} will add license \'{1}\', '
                                    'and assign it to cluster \'{2}\'.'
                                    ''.format(name, license_name, display_name))
                    log.info(comments[-1])
                    continue
                else:
                    try:
                        existing_license = __salt__['vsphere.add_license'](
                            key=license, description=license_name,
                            service_instance=si)
                    except salt.exceptions.VMwareApiError as ex:
                        comments.append(ex.err_msg)
                        log.error(comments[-1])
                        has_errors = True
                        continue
                    comments.append('Added license \'{0}\'.'
                                    ''.format(license_name))
                    log.info(comments[-1])
            else:
                # License exists let's check if it's assigned to the cluster
                comments.append('License \'{0}\' already exists. '
                                'Nothing to be done.'.format(license_name))
                log.info(comments[-1])
                existing_license = filtered_licenses[0]

            log.trace('Checking licensed entities...')
            assigned_licenses = __salt__['vsphere.list_assigned_licenses'](
                entity=entity,
                entity_display_name=display_name,
                service_instance=si)

            # Checking if any of the licenses already assigned have the same
            # name as the new license; the already assigned license would be
            # replaced by the new license
            #
            # Licenses with different names but matching features would be
            # replaced as well, but searching for those would be very complex
            #
            # the name check if good enough for now
            already_assigned_license = assigned_licenses[0] if \
                    assigned_licenses else None

            if already_assigned_license and \
               already_assigned_license['key'] == license:

                # License is already assigned to entity
                comments.append('License \'{0}\' already assigned to '
                                'cluster \'{1}\'. Nothing to be done.'
                                ''.format(license_name, display_name))
                log.info(comments[-1])
                continue

            needs_changes = True
            # License needs to be assigned to entity

            if existing_license['capacity'] <= existing_license['used']:
                # License is already fully used
                comments.append('Cannot assign license \'{0}\' to cluster '
                                '\'{1}\'. No free capacity available.'
                                ''.format(license_name, display_name))
                log.error(comments[-1])
                has_errors = True
                continue

            # Assign license
            if __opts__['test']:
                comments.append('State {0} will assign license \'{1}\' '
                                'to cluster \'{2}\'.'.format(
                                    name, license_name, display_name))
                log.info(comments[-1])
            else:
                try:
                    __salt__['vsphere.assign_license'](
                        license_key=license,
                        license_name=license_name,
                        entity=entity,
                        entity_display_name=display_name,
                        service_instance=si)
                except salt.exceptions.VMwareApiError as ex:
                    comments.append(ex.err_msg)
                    log.error(comments[-1])
                    has_errors = True
                    continue
                comments.append('Assigned license \'{0}\' to cluster \'{1}\'.'
                                ''.format(license_name, display_name))
                log.info(comments[-1])
                # Note: Because the already_assigned_license was retrieved
                # from the assignment license manager it doesn't have a used
                # value - that's a limitation from VMware. The license would
                # need to be retrieved again from the license manager to get
                # the value

                # Hide license keys
                assigned_license = __salt__['vsphere.list_assigned_licenses'](
                    entity=entity,
                    entity_display_name=display_name,
                    service_instance=si)[0]
                assigned_license['key'] = '<hidden>'
                if already_assigned_license:
                    already_assigned_license['key'] = '<hidden>'
                if already_assigned_license and \
                   already_assigned_license['capacity'] == sys.maxsize:

                    already_assigned_license['capacity'] = 'Unlimited'

                changes[license_name] = {'new': assigned_license,
                                         'old': already_assigned_license}
            continue
        __salt__['vsphere.disconnect'](si)

        ret.update({'result': True if (not needs_changes) else None if
                        __opts__['test'] else False if has_errors else True,
                    'comment': '\n'.join(comments),
                    'changes': changes if not __opts__['test'] else {}})

        return ret
    except salt.exceptions.CommandExecutionError as exc:
        log.exception('Encountered error')
        if si:
            __salt__['vsphere.disconnect'](si)
        ret.update({
            'result': False,
            'comment': exc.strerror})
        return ret