%PDF- %PDF-
Direktori : /usr/lib/python2.7/site-packages/salt/states/ |
Current File : //usr/lib/python2.7/site-packages/salt/states/smartos.py |
# -*- 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 docker.example.org: smartos.vm_present: - config: auto_import: true reprovision: true - vmconfig: image_uuid: emby/embyserver:latest brand: lx alias: mydockervm quota: 5 max_physical_memory: 1024 tags: label: 'my emby docker' owner: 'sjorge' resolvers: - 172.16.1.1 nics: "82:1b:8e:49:e9:18": nic_tag: trunk mtu: 1500 ips: - 172.16.1.118/24 vlan_id: 10 filesystems: "/config: source: "/vmdata/emby_config" type: lofs options: - nodevices 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 json 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 _split_docker_uuid(uuid): ''' Split a smartos docker uuid into repo and tag ''' if uuid: uuid = uuid.split(':') if len(uuid) == 2: tag = uuid[1] repo = uuid[0] if len(repo.split('/')) == 2: return repo, tag return None, None def _is_uuid(uuid): ''' Check if uuid is a valid smartos uuid Example: e69a0918-055d-11e5-8912-e3ceb6df4cf8 ''' if uuid and list((len(x) for x in uuid.split('-'))) == [8, 4, 4, 4, 12]: return True return False def _is_docker_uuid(uuid): ''' Check if uuid is a valid smartos docker uuid Example plexinc/pms-docker:plexpass ''' repo, tag = _split_docker_uuid(uuid) return not (not repo and not tag) 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 _copy_lx_vars(vmconfig): # NOTE: documentation on dockerinit: https://github.com/joyent/smartos-live/blob/master/src/dockerinit/README.md if 'image_uuid' in vmconfig: # NOTE: retrieve tags and type from image imgconfig = __salt__['imgadm.get'](vmconfig['image_uuid']).get('manifest', {}) imgtype = imgconfig.get('type', 'zone-dataset') imgtags = imgconfig.get('tags', {}) # NOTE: copy kernel_version (if not specified in vmconfig) if 'kernel_version' not in vmconfig and 'kernel_version' in imgtags: vmconfig['kernel_version'] = imgtags['kernel_version'] # NOTE: copy docker vars if imgtype == 'docker': vmconfig['docker'] = True vmconfig['kernel_version'] = vmconfig.get('kernel_version', '4.3.0') if 'internal_metadata' not in vmconfig: vmconfig['internal_metadata'] = {} for var in imgtags.get('docker:config', {}): val = imgtags['docker:config'][var] var = 'docker:{0}'.format(var.lower()) # NOTE: skip empty values if not val: continue # NOTE: skip or merge user values if var == 'docker:env': try: val_config = json.loads( vmconfig['internal_metadata'].get(var, "") ) except ValueError as e: val_config = [] for config_env_var in val_config if isinstance(val_config, list) else json.loads(val_config): config_env_var = config_env_var.split('=') for img_env_var in val: if img_env_var.startswith('{0}='.format(config_env_var[0])): val.remove(img_env_var) val.append('='.join(config_env_var)) elif var in vmconfig['internal_metadata']: continue if isinstance(val, list): # NOTE: string-encoded JSON arrays vmconfig['internal_metadata'][var] = json.dumps(val) else: vmconfig['internal_metadata'][var] = val return vmconfig 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 ret['changes']: 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 ret['changes']: ret['result'] = _write_config(config) return ret def source_present(name, source_type='imgapi'): ''' Ensure an image source is present on the computenode name : string source url source_type : string source type (imgapi or docker) ''' ret = {'name': name, 'changes': {}, 'result': None, 'comment': ''} if name in __salt__['imgadm.sources'](): # source is present ret['result'] = True ret['comment'] = 'image source {0} is present'.format(name) else: # add new source if __opts__['test']: res = {} ret['result'] = True else: res = __salt__['imgadm.source_add'](name, source_type) ret['result'] = (name in res) if ret['result']: ret['comment'] = 'image source {0} added'.format(name) ret['changes'][name] = 'added' else: ret['comment'] = 'image source {0} not added'.format(name) if 'Error' in res: ret['comment'] = '{0}: {1}'.format(ret['comment'], res['Error']) return ret def source_absent(name): ''' Ensure an image source is absent on the computenode name : string source url ''' ret = {'name': name, 'changes': {}, 'result': None, 'comment': ''} if name not in __salt__['imgadm.sources'](): # source is absent ret['result'] = True ret['comment'] = 'image source {0} is absent'.format(name) else: # remove source if __opts__['test']: res = {} ret['result'] = True else: res = __salt__['imgadm.source_delete'](name) ret['result'] = (name not in res) if ret['result']: ret['comment'] = 'image source {0} deleted'.format(name) ret['changes'][name] = 'deleted' else: ret['comment'] = 'image source {0} not deleted'.format(name) if 'Error' in res: ret['comment'] = '{0}: {1}'.format(ret['comment'], res['Error']) 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 _is_docker_uuid(name) and __salt__['imgadm.docker_to_uuid'](name): # docker image was imported ret['result'] = True ret['comment'] = 'image {0} ({1}) is present'.format( name, __salt__['imgadm.docker_to_uuid'](name), ) elif name in __salt__['imgadm.list'](): # image was already imported ret['result'] = True ret['comment'] = 'image {0} is present'.format(name) else: # add image if _is_docker_uuid(name): # NOTE: we cannot query available docker images available_images = [name] else: available_images = __salt__['imgadm.avail']() if name in available_images: if __opts__['test']: ret['result'] = True res = {} if _is_docker_uuid(name): res['00000000-0000-0000-0000-000000000000'] = name else: res[name] = available_images[name] else: res = __salt__['imgadm.import'](name) if _is_uuid(name): ret['result'] = (name in res) elif _is_docker_uuid(name): ret['result'] = __salt__['imgadm.docker_to_uuid'](name) is not None if ret['result']: ret['comment'] = 'image {0} imported'.format(name) ret['changes'] = res else: ret['comment'] = 'image {0} was unable to be imported'.format(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': ''} uuid = None if _is_uuid(name): uuid = name if _is_docker_uuid(name): uuid = __salt__['imgadm.docker_to_uuid'](name) if not uuid or uuid not in __salt__['imgadm.list'](): # image not imported ret['result'] = True ret['comment'] = 'image {0} is absent'.format(name) else: # check if image in use by vm if uuid 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: image = __salt__['imgadm.get'](uuid) image_count = 0 if image['manifest']['name'] == 'docker-layer': # NOTE: docker images are made of multiple layers, loop over them while image: image_count += 1 __salt__['imgadm.delete'](image['manifest']['uuid']) if 'origin' in image['manifest']: image = __salt__['imgadm.get'](image['manifest']['origin']) else: image = None else: # NOTE: normal images can just be delete __salt__['imgadm.delete'](uuid) ret['result'] = uuid not in __salt__['imgadm.list']() if image_count: ret['comment'] = 'image {0} and {1} children deleted'.format(name, image_count) else: 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 .. warning:: Only image_present states that are included via the top file will be detected. ''' 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: if _is_uuid(state['name']): images.append(state['name']) elif _is_docker_uuid(state['name']): state['name'] = __salt__['imgadm.docker_to_uuid'](state['name']) if not state['name']: continue images.append(state['name']) # retrieve images in use by vms for image_uuid in __salt__['vmadm.list'](order='image_uuid'): if image_uuid not in images: images.append(image_uuid) # purge unused images ret['result'] = True for image_uuid in __salt__['imgadm.list'](): if image_uuid in images: continue image = __salt__['imgadm.get'](image_uuid) if image['manifest']['name'] == 'docker-layer': # NOTE: docker images are made of multiple layers, loop over them while image: image_uuid = image['manifest']['uuid'] 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 'origin' in image['manifest']: image = __salt__['imgadm.get'](image['manifest']['origin']) else: image = None else: # NOTE: normal images can just be delete 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 not ret['changes']: ret['comment'] = 'no images deleted' elif ret['result'] and ret['changes']: 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 - 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']) # 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 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