text: # -*- coding: utf-8 -*-
  '''
  Management of SmartOS Standalone Compute Nodes
  
  :maintainer:    Jorge Schrauwen <sjorge@blackdot.be>
  :maturity:      new
  :depends:       vmadm, imgadm
  :platform:      smartos
  
  .. versionadded:: 2016.3.0
  
  .. code-block:: yaml
  
      vmtest.example.org:
        smartos.vm_present:
          - config:
              reprovision: true
          - vmconfig:
              image_uuid: c02a2044-c1bd-11e4-bd8c-dfc1db8b0182
              brand: joyent
              alias: vmtest
              quota: 5
              max_physical_memory: 512
              tags:
                label: 'test vm'
                owner: 'sjorge'
              nics:
                "82:1b:8e:49:e9:12":
                  nic_tag: trunk
                  mtu: 1500
                  ips:
                    - 172.16.1.123/16
                    - 192.168.2.123/24
                  vlan_id: 10
                "82:1b:8e:49:e9:13":
                  nic_tag: trunk
                  mtu: 1500
                  ips:
                    - dhcp
                  vlan_id: 30
              filesystems:
                "/bigdata":
                  source: "/bulk/data"
                  type: lofs
                  options:
                    - ro
                    - nodevices
  
      kvmtest.example.org:
        smartos.vm_present:
          - vmconfig:
              brand: kvm
              alias: kvmtest
              cpu_type: host
              ram: 512
              vnc_port: 9
              tags:
                label: 'test kvm'
                owner: 'sjorge'
              disks:
                disk0
                  size: 2048
                  model: virtio
                  compression: lz4
                  boot: true
              nics:
                "82:1b:8e:49:e9:15":
                  nic_tag: trunk
                  mtu: 1500
                  ips:
                    - dhcp
                  vlan_id: 30
  
      cleanup_images:
        smartos.image_vacuum
  
  .. note::
  
      Keep in mind that when removing properties from vmconfig they will not get
      removed from the vm's current configuration, except for nics, disk, tags, ...
      they get removed via add_*, set_*, update_*, and remove_*. Properties must
      be manually reset to their default value.
      The same behavior as when using 'vmadm update'.
  '''
  from __future__ import absolute_import, unicode_literals, print_function
  
  # Import Python libs
  import logging
  import os
  
  # Import Salt libs
  import salt.utils.atomicfile
  import salt.utils.data
  import salt.utils.files
  
  # Import 3rd party libs
  from salt.ext import six
  
  log = logging.getLogger(__name__)
  
  # Define the state's virtual name
  __virtualname__ = 'smartos'
  
  
  def __virtual__():
      '''
      Provides smartos state provided for SmartOS
      '''
      if 'vmadm.create' in __salt__ and 'imgadm.list' in __salt__:
          return True
      else:
          return (
              False,
              '{0} state module can only be loaded on SmartOS compute nodes'.format(
                  __virtualname__
              )
          )
  
  
  def _load_config():
      '''
      Loads and parses /usbkey/config
      '''
      config = {}
  
      if os.path.isfile('/usbkey/config'):
          with salt.utils.files.fopen('/usbkey/config', 'r') as config_file:
              for optval in config_file:
                  optval = salt.utils.stringutils.to_unicode(optval)
                  if optval[0] == '#':
                      continue
                  if '=' not in optval:
                      continue
                  optval = optval.split('=')
                  config[optval[0].lower()] = optval[1].strip().strip('"')
      log.debug('smartos.config - read /usbkey/config: %s', config)
      return config
  
  
  def _write_config(config):
      '''
      writes /usbkey/config
      '''
      try:
          with salt.utils.atomicfile.atomic_open('/usbkey/config', 'w') as config_file:
              config_file.write("#\n# This file was generated by salt\n#\n")
              for prop in salt.utils.odict.OrderedDict(sorted(config.items())):
                  if ' ' in six.text_type(config[prop]):
                      if not config[prop].startswith('"') or not config[prop].endswith('"'):
                          config[prop] = '"{0}"'.format(config[prop])
                  config_file.write(
                      salt.utils.stringutils.to_str(
                          "{0}={1}\n".format(prop, config[prop])
                      )
                  )
          log.debug('smartos.config - wrote /usbkey/config: %s', config)
      except IOError:
          return False
  
      return True
  
  
  def _parse_vmconfig(config, instances):
      '''
      Parse vm_present vm config
      '''
      vmconfig = None
  
      if isinstance(config, (salt.utils.odict.OrderedDict)):
          vmconfig = salt.utils.odict.OrderedDict()
          for prop in config:
              if prop not in instances:
                  vmconfig[prop] = config[prop]
              else:
                  if not isinstance(config[prop], (salt.utils.odict.OrderedDict)):
                      continue
                  vmconfig[prop] = []
                  for instance in config[prop]:
                      instance_config = config[prop][instance]
                      instance_config[instances[prop]] = instance
                      ## some property are lowercase
                      if 'mac' in instance_config:
                          instance_config['mac'] = instance_config['mac'].lower()
                      vmconfig[prop].append(instance_config)
      else:
          log.error('smartos.vm_present::parse_vmconfig - failed to parse')
  
      return vmconfig
  
  
  def _get_instance_changes(current, state):
      '''
      get modified properties
      '''
      # get keys
      current_keys = set(current.keys())
      state_keys = set(state.keys())
  
      # compare configs
      changed = salt.utils.data.compare_dicts(current, state)
      for change in salt.utils.data.compare_dicts(current, state):
          if change in changed and changed[change]['old'] == "":
              del changed[change]
          if change in changed and changed[change]['new'] == "":
              del changed[change]
  
      return changed
  
  
  def config_present(name, value):
      '''
      Ensure configuration property is set to value in /usbkey/config
  
      name : string
          name of property
      value : string
          value of property
  
      '''
      name = name.lower()
      ret = {'name': name,
             'changes': {},
             'result': None,
             'comment': ''}
  
      # load confiration
      config = _load_config()
  
      # handle bool and None value
      if isinstance(value, (bool)):
          value = 'true' if value else 'false'
      if not value:
          value = ""
  
      if name in config:
          if six.text_type(config[name]) == six.text_type(value):
              # we're good
              ret['result'] = True
              ret['comment'] = 'property {0} already has value "{1}"'.format(name, value)
          else:
              # update property
              ret['result'] = True
              ret['comment'] = 'updated property {0} with value "{1}"'.format(name, value)
              ret['changes'][name] = value
              config[name] = value
      else:
          # add property
          ret['result'] = True
          ret['comment'] = 'added property {0} with value "{1}"'.format(name, value)
          ret['changes'][name] = value
          config[name] = value
  
      # apply change if needed
      if not __opts__['test'] and len(ret['changes']) > 0:
          ret['result'] = _write_config(config)
  
      return ret
  
  
  def config_absent(name):
      '''
      Ensure configuration property is absent in /usbkey/config
  
      name : string
          name of property
  
      '''
      name = name.lower()
      ret = {'name': name,
             'changes': {},
             'result': None,
             'comment': ''}
  
      # load configuration
      config = _load_config()
  
      if name in config:
          # delete property
          ret['result'] = True
          ret['comment'] = 'property {0} deleted'.format(name)
          ret['changes'][name] = None
          del config[name]
      else:
          # we're good
          ret['result'] = True
          ret['comment'] = 'property {0} is absent'.format(name)
  
      # apply change if needed
      if not __opts__['test'] and len(ret['changes']) > 0:
          ret['result'] = _write_config(config)
  
      return ret
  
  
  def image_present(name):
      '''
      Ensure image is present on the computenode
  
      name : string
          uuid of image
      '''
      ret = {'name': name,
             'changes': {},
             'result': None,
             'comment': ''}
  
      if name in __salt__['imgadm.list']():
          # we're good
          ret['result'] = True
          ret['comment'] = 'image {0} is present'.format(name)
      else:
          # add image
          available_images = __salt__['imgadm.avail']()
          if name in available_images:
              if __opts__['test']:
                  ret['result'] = True
              else:
                  __salt__['imgadm.import'](name)
                  ret['result'] = (name in __salt__['imgadm.list']())
              ret['comment'] = 'image {0} installed'.format(name)
              ret['changes'][name] = available_images[name]
          else:
              ret['result'] = False
              ret['comment'] = 'image {0} does not exists'.format(name)
  
      return ret
  
  
  def image_absent(name):
      '''
      Ensure image is absent on the computenode
  
      name : string
          uuid of image
  
      .. note::
  
          computenode.image_absent will only remove the image if it is not used
          by a vm.
      '''
      ret = {'name': name,
             'changes': {},
             'result': None,
             'comment': ''}
  
      if name not in __salt__['imgadm.list']():
          # we're good
          ret['result'] = True
          ret['comment'] = 'image {0} is absent'.format(name)
      else:
          # check if image in use by vm
          if name in __salt__['vmadm.list'](order='image_uuid'):
              ret['result'] = False
              ret['comment'] = 'image {0} currently in use by a vm'.format(name)
          else:
              # delete image
              if __opts__['test']:
                  ret['result'] = True
              else:
                  __salt__['imgadm.delete'](name)
                  ret['result'] = name not in __salt__['imgadm.list']()
              ret['comment'] = 'image {0} deleted'.format(name)
              ret['changes'][name] = None
  
      return ret
  
  
  def image_vacuum(name):
      '''
      Delete images not in use or installed via image_present
      '''
      name = name.lower()
      ret = {'name': name,
             'changes': {},
             'result': None,
             'comment': ''}
  
      # list of images to keep
      images = []
  
      # retrieve image_present state data for host
      for state in __salt__['state.show_lowstate']():
          # don't throw exceptions when not highstate run
          if 'state' not in state:
              continue
  
          # skip if not from this state module
          if state['state'] != __virtualname__:
              continue
          # skip if not image_present
          if state['fun'] not in ['image_present']:
              continue
          # keep images installed via image_present
          if 'name' in state:
              images.append(state['name'])
  
      # retrieve images in use by vms
      for image_uuid in __salt__['vmadm.list'](order='image_uuid'):
          if image_uuid in images:
              continue
          images.append(image_uuid)
  
      # purge unused images
      ret['result'] = True
      for image_uuid in __salt__['imgadm.list']():
          if image_uuid in images:
              continue
          if image_uuid in __salt__['imgadm.delete'](image_uuid):
              ret['changes'][image_uuid] = None
          else:
              ret['result'] = False
              ret['comment'] = 'failed to delete images'
  
      if ret['result'] and len(ret['changes']) == 0:
          ret['comment'] = 'no images deleted'
      elif ret['result'] and len(ret['changes']) > 0:
          ret['comment'] = 'images deleted'
  
      return ret
  
  
  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
            - reprovision (false) - reprovision on image_uuid changes
            - enforce_customer_metadata (true) - false = add metadata only, true =  add, update, and remove metadata
            - enforce_tags (true) - false = add tags only, true =  add, update, and remove tags
  
      .. 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,...
  
      '''
      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,
          'reprovision': False,
      }
      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'
          ]
      }
  
      # 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
  
      # 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 = True
              if 'enforce_{0}'.format(collection) in config:
                  enforce = config['enforce_{0}'.format(collection)]
              log.debug('smartos.vm_present::enforce_%s = %s', collection, enforce)
  
              # 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
                              if len(changed) > 0:
                                  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 len(state_cfg[prop]) == 0:
                                      continue
  
                                  if prop not in current_cfg:
                                      update_cfg[prop] = state_cfg[prop]
  
                              # update instance
                              if len(update_cfg) > 0:
                                  # 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 len(vmconfig['changed']) > 0:
              # 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 len(ret['changes'][vmconfig['state']['hostname']]) > 0:
                  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'])
  
              if 'image_uuid' in vmconfig['current'] and vmconfig['reprovision_uuid'] != vmconfig['current']['image_uuid']:
                  if config['reprovision']:
                      # check required image installed
                      if vmconfig['reprovision_uuid'] not in __salt__['imgadm.list']():
                          if config['auto_import']:
                              # check if image is available
                              available_images = __salt__['imgadm.avail']()
                              if vmconfig['reprovision_uuid'] in available_images and not __opts__['test']:
                                  # import image
                                  __salt__['imgadm.import'](vmconfig['reprovision_uuid'])
  
                      # 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
                          if vmconfig['reprovision_uuid'] not in __salt__['imgadm.list']():
                              ret['comment'] = 'vm {0} updated, reprovision failed because images {1} not installed'.format(
                                  vmconfig['state']['hostname'],
                                  vmconfig['reprovision_uuid']
                              )
                          else:
                              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
          if 'image_uuid' in vmconfig and vmconfig['image_uuid'] not in __salt__['imgadm.list']():
              if config['auto_import']:
                  # check if image is available
                  available_images = __salt__['imgadm.avail']()
                  if vmconfig['image_uuid'] not in available_images:
                      ret['result'] = False
                      ret['comment'] = 'image {0} not available'.format(vmconfig['image_uuid'])
                  elif not __opts__['test']:
                      if vmconfig['image_uuid'] not in __salt__['imgadm.import'](vmconfig['image_uuid']):
                          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'])
  
          # 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
  
  
  def vm_absent(name, archive=False):
      '''
      Ensure vm is absent on the computenode
  
      name : string
          hostname of vm
      archive : boolean
          toggle archiving of vm on removal
  
      .. note::
  
          State ID is used as hostname. Hostnames must be unique.
  
      '''
      name = name.lower()
      ret = {'name': name,
             'changes': {},
             'result': None,
             'comment': ''}
  
      if name not in __salt__['vmadm.list'](order='hostname'):
          # we're good
          ret['result'] = True
          ret['comment'] = 'vm {0} is absent'.format(name)
      else:
          # delete vm
          if not __opts__['test']:
              # set archive to true if needed
              if archive:
                  __salt__['vmadm.update'](vm=name, key='hostname', archive_on_delete=True)
  
              ret['result'] = __salt__['vmadm.delete'](name, key='hostname')
          else:
              ret['result'] = True
  
          if not isinstance(ret['result'], bool) and ret['result'].get('Error'):
              ret['result'] = False
              ret['comment'] = 'failed to delete vm {0}'.format(name)
          else:
              ret['comment'] = 'vm {0} deleted'.format(name)
              ret['changes'][name] = None
  
      return ret
  
  
  def vm_running(name):
      '''
      Ensure vm is in the running state on the computenode
  
      name : string
          hostname of vm
  
      .. note::
  
          State ID is used as hostname. Hostnames must be unique.
  
      '''
      name = name.lower()
      ret = {'name': name,
             'changes': {},
             'result': None,
             'comment': ''}
  
      if name in __salt__['vmadm.list'](order='hostname', search='state=running'):
          # we're good
          ret['result'] = True
          ret['comment'] = 'vm {0} already running'.format(name)
      else:
          # start the vm
          ret['result'] = True if __opts__['test'] else __salt__['vmadm.start'](name, key='hostname')
          if not isinstance(ret['result'], bool) and ret['result'].get('Error'):
              ret['result'] = False
              ret['comment'] = 'failed to start {0}'.format(name)
          else:
              ret['changes'][name] = 'running'
              ret['comment'] = 'vm {0} started'.format(name)
  
      return ret
  
  
  def vm_stopped(name):
      '''
      Ensure vm is in the stopped state on the computenode
  
      name : string
          hostname of vm
  
      .. note::
  
          State ID is used as hostname. Hostnames must be unique.
  
      '''
      name = name.lower()
      ret = {'name': name,
             'changes': {},
             'result': None,
             'comment': ''}
  
      if name in __salt__['vmadm.list'](order='hostname', search='state=stopped'):
          # we're good
          ret['result'] = True
          ret['comment'] = 'vm {0} already stopped'.format(name)
      else:
          # stop the vm
          ret['result'] = True if __opts__['test'] else __salt__['vmadm.stop'](name, key='hostname')
          if not isinstance(ret['result'], bool) and ret['result'].get('Error'):
              ret['result'] = False
              ret['comment'] = 'failed to stop {0}'.format(name)
          else:
              ret['changes'][name] = 'stopped'
              ret['comment'] = 'vm {0} stopped'.format(name)
  
      return ret
  
  # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
  