<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:

def snapshot_present(name, recursive=False, properties=None):
    '''
    ensure snapshot exists and has properties set

    name : string
        name of snapshot
    recursive : boolean
        recursively create snapshots of all descendent datasets
    properties : dict
        additional zfs properties (-o)

    .. note:
        Properties are only set at creation time

    '''
    ret = {'name': name,
           'changes': {},
           'result': True,
           'comment': ''}

    ## log configuration
    log.debug('zfs.snapshot_present::%s::config::recursive = %s',
              name, recursive)
    log.debug('zfs.snapshot_present::%s::config::properties = %s',
              name, properties)

    ## ensure properties are zfs values
    if properties:
        properties = __utils__['zfs.from_auto_dict'](properties)

    ## check we have valid snapshot name
    if not __utils__['zfs.is_snapshot'](name):
        ret['result'] = False
        ret['comment'] = 'invalid snapshot name: {0}'.format(name)
        return ret

    ## ensure snapshot exits
    if not __salt__['zfs.exists'](name, **{'type': 'snapshot'}):
        ## NOTE: create the snapshot
        if not __opts__['test']:
            mod_res = __salt__['zfs.snapshot'](name, **{'recursive': recursive, 'properties': properties})
        else:
            mod_res = OrderedDict([('snapshotted', True)])

        ret['result'] = mod_res['snapshotted']
        if ret['result']:
            ret['changes'][name] = 'snapshotted'
            if properties:
                ret['changes'][name] = properties
            ret['comment'] = 'snapshot {0} was created'.format(name)
        else:
            ret['comment'] = 'failed to create snapshot {0}'.format(name)
            if 'error' in mod_res:
                ret['comment'] = mod_res['error']
    else:
        ## NOTE: snapshot already exists
        ret['comment'] = 'snapshot is present'

    return ret