docs: def vm_present(name, vmconfig, config=None):
      '''
      Ensure vm is present on the computenode
  
      name : string
          hostname of vm
      vmconfig : dict
          options to set for the vm
      config : dict
          fine grain control over vm_present
  
      .. note::
  
          The following configuration properties can be toggled in the config parameter.
            - kvm_reboot (true)                - reboots of kvm zones if needed for a config update
            - auto_import (false)              - automatic importing of missing images
            - auto_lx_vars (true)              - copy kernel_version and docker:* variables from image
            - reprovision (false)              - reprovision on image_uuid changes
            - enforce_tags (true)              - false = add tags only, true =  add, update, and remove tags
            - enforce_routes (true)            - false = add tags only, true =  add, update, and remove routes
            - enforce_internal_metadata (true) - false = add metadata only, true =  add, update, and remove metadata
            - enforce_customer_metadata (true) - false = add metadata only, true =  add, update, and remove metadata
  
      .. note::
  
          State ID is used as hostname. Hostnames must be unique.
  
      .. note::
  
          If hostname is provided in vmconfig this will take president over the State ID.
          This allows multiple states to be applied to the same vm.
  
      .. note::
  
          The following instances should have a unique ID.
            - nic : mac
            - filesystem: target
            - disk : path or diskN for zvols
  
          e.g. disk0 will be the first disk added, disk1 the 2nd,...
  
      .. versionchanged:: 2019.2.0
  
          Added support for docker image uuids, added auto_lx_vars configuration, documented some missing configuration options.
  
      '''
      name = name.lower()
      ret = {'name': name,
             'changes': {},
             'result': None,
             'comment': ''}
  
      # config defaults
      state_config = config if config else {}
      config = {
          'kvm_reboot': True,
          'auto_import': False,
          'auto_lx_vars': True,
          'reprovision': False,
          'enforce_tags': True,
          'enforce_routes': True,
          'enforce_internal_metadata': True,
          'enforce_customer_metadata': True,
      }
      config.update(state_config)
      log.debug('smartos.vm_present::%s::config - %s', name, config)
  
      # map special vmconfig parameters
      #  collections have set/remove handlers
      #  instances have add/update/remove handlers and a unique id
      vmconfig_type = {
          'collection': [
              'tags',
              'customer_metadata',
              'internal_metadata',
              'routes'
          ],
          'instance': {
              'nics': 'mac',
              'disks': 'path',
              'filesystems': 'target'
          },
          'create_only': [
              'filesystems'
          ]
      }
      vmconfig_docker_keep = [
          'docker:id',
          'docker:restartcount',
      ]
      vmconfig_docker_array = [
          'docker:env',
          'docker:cmd',
          'docker:entrypoint',
      ]
  
      # parse vmconfig
      vmconfig = _parse_vmconfig(vmconfig, vmconfig_type['instance'])
      log.debug('smartos.vm_present::%s::vmconfig - %s', name, vmconfig)
  
      # set hostname if needed
      if 'hostname' not in vmconfig:
          vmconfig['hostname'] = name
  
      # prepare image_uuid
      if 'image_uuid' in vmconfig:
          # NOTE: lookup uuid from docker uuid (normal uuid's are passed throuhg unmodified)
          #       we must do this again if we end up importing a missing image later!
          docker_uuid = __salt__['imgadm.docker_to_uuid'](vmconfig['image_uuid'])
          vmconfig['image_uuid'] = docker_uuid if docker_uuid else vmconfig['image_uuid']
  
          # NOTE: import image (if missing and allowed)
          if vmconfig['image_uuid'] not in __salt__['imgadm.list']():
              if config['auto_import']:
                  if not __opts__['test']:
                      res = __salt__['imgadm.import'](vmconfig['image_uuid'])
                      vmconfig['image_uuid'] = __salt__['imgadm.docker_to_uuid'](vmconfig['image_uuid'])
                      if vmconfig['image_uuid'] not in res:
                          ret['result'] = False
                          ret['comment'] = 'failed to import image {0}'.format(vmconfig['image_uuid'])
              else:
                  ret['result'] = False
                  ret['comment'] = 'image {0} not installed'.format(vmconfig['image_uuid'])
  
      # prepare disk.*.image_uuid
      for disk in vmconfig['disks'] if 'disks' in vmconfig else []:
          if 'image_uuid' in disk and disk['image_uuid'] not in __salt__['imgadm.list']():
              if config['auto_import']:
                  if not __opts__['test']:
                      res = __salt__['imgadm.import'](disk['image_uuid'])
                      if disk['image_uuid'] not in res:
                          ret['result'] = False
                          ret['comment'] = 'failed to import image {0}'.format(disk['image_uuid'])
              else:
                  ret['result'] = False
                  ret['comment'] = 'image {0} not installed'.format(disk['image_uuid'])
  
      # docker json-array handling
      if 'internal_metadata' in vmconfig:
          for var in vmconfig_docker_array:
              if var not in vmconfig['internal_metadata']:
                  continue
              if isinstance(vmconfig['internal_metadata'][var], list):
                  vmconfig['internal_metadata'][var] = json.dumps(
                      vmconfig['internal_metadata'][var]
                  )
  
      # copy lx variables
      if vmconfig['brand'] == 'lx' and config['auto_lx_vars']:
          # NOTE: we can only copy the lx vars after the image has bene imported
          vmconfig = _copy_lx_vars(vmconfig)
  
      # quick abort if things look wrong
      # NOTE: use explicit check for false, otherwise None also matches!
      if ret['result'] is False:
          return ret
  
      # check if vm exists
      if vmconfig['hostname'] in __salt__['vmadm.list'](order='hostname'):
          # update vm
          ret['result'] = True
  
          # expand vmconfig
          vmconfig = {
              'state': vmconfig,
              'current': __salt__['vmadm.get'](vmconfig['hostname'], key='hostname'),
              'changed': {},
              'reprovision_uuid': None
          }
  
          # prepare reprovision
          if 'image_uuid' in vmconfig['state']:
              vmconfig['reprovision_uuid'] = vmconfig['state']['image_uuid']
              vmconfig['state']['image_uuid'] = vmconfig['current']['image_uuid']
  
          # disks need some special care
          if 'disks' in vmconfig['state']:
              new_disks = []
              for disk in vmconfig['state']['disks']:
                  path = False
                  if 'disks' in vmconfig['current']:
                      for cdisk in vmconfig['current']['disks']:
                          if cdisk['path'].endswith(disk['path']):
                              path = cdisk['path']
                              break
                  if not path:
                      del disk['path']
                  else:
                      disk['path'] = path
                  new_disks.append(disk)
              vmconfig['state']['disks'] = new_disks
  
          # process properties
          for prop in vmconfig['state']:
              # skip special vmconfig_types
              if prop in vmconfig_type['instance'] or \
                  prop in vmconfig_type['collection'] or \
                  prop in vmconfig_type['create_only']:
                  continue
  
              # skip unchanged properties
              if prop in vmconfig['current']:
                  if isinstance(vmconfig['current'][prop], (list)) or isinstance(vmconfig['current'][prop], (dict)):
                      if vmconfig['current'][prop] == vmconfig['state'][prop]:
                          continue
                  else:
                      if "{0}".format(vmconfig['current'][prop]) == "{0}".format(vmconfig['state'][prop]):
                          continue
  
              # add property to changeset
              vmconfig['changed'][prop] = vmconfig['state'][prop]
  
          # process collections
          for collection in vmconfig_type['collection']:
              # skip create only collections
              if collection in vmconfig_type['create_only']:
                  continue
  
              # enforcement
              enforce = config['enforce_{0}'.format(collection)]
              log.debug('smartos.vm_present::enforce_%s = %s', collection, enforce)
  
              # dockerinit handling
              if collection == 'internal_metadata' and vmconfig['state'].get('docker', False):
                  if 'internal_metadata' not in vmconfig['state']:
                      vmconfig['state']['internal_metadata'] = {}
  
                  # preserve some docker specific metadata (added and needed by dockerinit)
                  for var in vmconfig_docker_keep:
                      val = vmconfig['current'].get(collection, {}).get(var, None)
                      if val is not None:
                          vmconfig['state']['internal_metadata'][var] = val
  
              # process add and update for collection
              if collection in vmconfig['state'] and vmconfig['state'][collection] is not None:
                  for prop in vmconfig['state'][collection]:
                      # skip unchanged properties
                      if prop in vmconfig['current'][collection] and \
                          vmconfig['current'][collection][prop] == vmconfig['state'][collection][prop]:
                          continue
  
                      # skip update if not enforcing
                      if not enforce and prop in vmconfig['current'][collection]:
                          continue
  
                      # create set_ dict
                      if 'set_{0}'.format(collection) not in vmconfig['changed']:
                          vmconfig['changed']['set_{0}'.format(collection)] = {}
  
                      # add property to changeset
                      vmconfig['changed']['set_{0}'.format(collection)][prop] = vmconfig['state'][collection][prop]
  
              # process remove for collection
              if enforce and collection in vmconfig['current'] and vmconfig['current'][collection] is not None:
                  for prop in vmconfig['current'][collection]:
                      # skip if exists in state
                      if collection in vmconfig['state'] and vmconfig['state'][collection] is not None:
                          if prop in vmconfig['state'][collection]:
                              continue
  
                      # create remove_ array
                      if 'remove_{0}'.format(collection) not in vmconfig['changed']:
                          vmconfig['changed']['remove_{0}'.format(collection)] = []
  
                      # remove property
                      vmconfig['changed']['remove_{0}'.format(collection)].append(prop)
  
          # process instances
          for instance in vmconfig_type['instance']:
              # skip create only instances
              if instance in vmconfig_type['create_only']:
                  continue
  
              # add or update instances
              if instance in vmconfig['state'] and vmconfig['state'][instance] is not None:
                  for state_cfg in vmconfig['state'][instance]:
                      add_instance = True
  
                      # find instance with matching ids
                      for current_cfg in vmconfig['current'][instance]:
                          if vmconfig_type['instance'][instance] not in state_cfg:
                              continue
  
                          if state_cfg[vmconfig_type['instance'][instance]] == current_cfg[vmconfig_type['instance'][instance]]:
                              # ids have matched, disable add instance
                              add_instance = False
  
                              changed = _get_instance_changes(current_cfg, state_cfg)
                              update_cfg = {}
  
                              # handle changes
                              for prop in changed:
                                  update_cfg[prop] = state_cfg[prop]
  
                              # handle new properties
                              for prop in state_cfg:
                                  # skip empty props like ips, options,..
                                  if isinstance(state_cfg[prop], (list)) and not state_cfg[prop]:
                                      continue
  
                                  if prop not in current_cfg:
                                      update_cfg[prop] = state_cfg[prop]
  
                              # update instance
                              if update_cfg:
                                  # create update_ array
                                  if 'update_{0}'.format(instance) not in vmconfig['changed']:
                                      vmconfig['changed']['update_{0}'.format(instance)] = []
  
                                  update_cfg[vmconfig_type['instance'][instance]] = state_cfg[vmconfig_type['instance'][instance]]
                                  vmconfig['changed']['update_{0}'.format(instance)].append(update_cfg)
  
                      if add_instance:
                          # create add_ array
                          if 'add_{0}'.format(instance) not in vmconfig['changed']:
                              vmconfig['changed']['add_{0}'.format(instance)] = []
  
                          # add instance
                          vmconfig['changed']['add_{0}'.format(instance)].append(state_cfg)
  
              # remove instances
              if instance in vmconfig['current'] and vmconfig['current'][instance] is not None:
                  for current_cfg in vmconfig['current'][instance]:
                      remove_instance = True
  
                      # find instance with matching ids
                      if instance in vmconfig['state'] and vmconfig['state'][instance] is not None:
                          for state_cfg in vmconfig['state'][instance]:
                              if vmconfig_type['instance'][instance] not in state_cfg:
                                  continue
  
                              if state_cfg[vmconfig_type['instance'][instance]] == current_cfg[vmconfig_type['instance'][instance]]:
                                  # keep instance if matched
                                  remove_instance = False
  
                      if remove_instance:
                          # create remove_ array
                          if 'remove_{0}'.format(instance) not in vmconfig['changed']:
                              vmconfig['changed']['remove_{0}'.format(instance)] = []
  
                          # remove instance
                          vmconfig['changed']['remove_{0}'.format(instance)].append(
                              current_cfg[vmconfig_type['instance'][instance]]
                          )
  
          # update vm if we have pending changes
          kvm_needs_start = False
          if not __opts__['test'] and vmconfig['changed']:
              # stop kvm if disk updates and kvm_reboot
              if vmconfig['current']['brand'] == 'kvm' and config['kvm_reboot']:
                  if 'add_disks' in vmconfig['changed'] or \
                      'update_disks' in vmconfig['changed'] or \
                      'remove_disks' in vmconfig['changed']:
                      if vmconfig['state']['hostname'] in __salt__['vmadm.list'](order='hostname', search='state=running'):
                          kvm_needs_start = True
                          __salt__['vmadm.stop'](vm=vmconfig['state']['hostname'], key='hostname')
  
              # do update
              rret = __salt__['vmadm.update'](vm=vmconfig['state']['hostname'], key='hostname', **vmconfig['changed'])
              if not isinstance(rret, (bool)) and 'Error' in rret:
                  ret['result'] = False
                  ret['comment'] = "{0}".format(rret['Error'])
              else:
                  ret['result'] = True
                  ret['changes'][vmconfig['state']['hostname']] = vmconfig['changed']
  
          if ret['result']:
              if __opts__['test']:
                  ret['changes'][vmconfig['state']['hostname']] = vmconfig['changed']
  
              if vmconfig['state']['hostname'] in ret['changes'] and ret['changes'][vmconfig['state']['hostname']]:
                  ret['comment'] = 'vm {0} updated'.format(vmconfig['state']['hostname'])
                  if config['kvm_reboot'] and vmconfig['current']['brand'] == 'kvm' and not __opts__['test']:
                      if vmconfig['state']['hostname'] in __salt__['vmadm.list'](order='hostname', search='state=running'):
                          __salt__['vmadm.reboot'](vm=vmconfig['state']['hostname'], key='hostname')
                      if kvm_needs_start:
                          __salt__['vmadm.start'](vm=vmconfig['state']['hostname'], key='hostname')
              else:
                  ret['changes'] = {}
                  ret['comment'] = 'vm {0} is up to date'.format(vmconfig['state']['hostname'])
  
              # reprovision (if required and allowed)
              if 'image_uuid' in vmconfig['current'] and vmconfig['reprovision_uuid'] != vmconfig['current']['image_uuid']:
                  if config['reprovision']:
                      rret = __salt__['vmadm.reprovision'](
                          vm=vmconfig['state']['hostname'],
                          key='hostname',
                          image=vmconfig['reprovision_uuid']
                      )
                      if not isinstance(rret, (bool)) and 'Error' in rret:
                          ret['result'] = False
                          ret['comment'] = 'vm {0} updated, reprovision failed'.format(
                              vmconfig['state']['hostname']
                          )
                      else:
                          ret['comment'] = 'vm {0} updated and reprovisioned'.format(vmconfig['state']['hostname'])
                          if vmconfig['state']['hostname'] not in ret['changes']:
                              ret['changes'][vmconfig['state']['hostname']] = {}
                          ret['changes'][vmconfig['state']['hostname']]['image_uuid'] = vmconfig['reprovision_uuid']
                  else:
                      log.warning('smartos.vm_present::%s::reprovision - '
                                  'image_uuid in state does not match current, '
                                  'reprovision not allowed',
                                  name)
          else:
              ret['comment'] = 'vm {0} failed to be updated'.format(vmconfig['state']['hostname'])
              if not isinstance(rret, (bool)) and 'Error' in rret:
                  ret['comment'] = "{0}".format(rret['Error'])
      else:
          # check required image installed
          ret['result'] = True
  
          # disks need some special care
          if 'disks' in vmconfig:
              new_disks = []
              for disk in vmconfig['disks']:
                  if 'path' in disk:
                      del disk['path']
                  new_disks.append(disk)
              vmconfig['disks'] = new_disks
  
          # create vm
          if ret['result']:
              uuid = __salt__['vmadm.create'](**vmconfig) if not __opts__['test'] else True
              if not isinstance(uuid, (bool)) and 'Error' in uuid:
                  ret['result'] = False
                  ret['comment'] = "{0}".format(uuid['Error'])
              else:
                  ret['result'] = True
                  ret['changes'][vmconfig['hostname']] = vmconfig
                  ret['comment'] = 'vm {0} created'.format(vmconfig['hostname'])
  
      return ret