Update the definition of an existing domain.

    :param name: Name of the domain to update
    :param cpu: Number of virtual CPUs to assign to the virtual machine
    :param mem: Amount of memory to allocate to the virtual machine in MiB.
    :param disk_profile: disk profile to use
    :param disks:
        Disk definitions as documented in the :func:`init` function.
        If neither the profile nor this parameter are defined, the disk devices
        will not be changed. However to clear disks set this parameter to empty list.

    :param nic_profile: network interfaces profile to use
    :param interfaces:
        Network interface definitions as documented in the :func:`init` function.
        If neither the profile nor this parameter are defined, the interface devices
        will not be changed. However to clear network interfaces set this parameter
        to empty list.

    :param graphics:
        The new graphics definition as defined in init-graphics-def_. If not set,
        the graphics will not be changed. To remove a graphics device, set this parameter
        to ``{'type': 'none'}``.

    :param live:
        ``False`` to avoid trying to live update the definition. In such a case, the
        new definition is applied at the next start of the virtual machine. If ``True``,
        not all aspects of the definition can be live updated, but as much as possible
        will be attempted. (Default: ``True``)

    :param connection: libvirt connection URI, overriding defaults
    :param username: username to connect with, overriding defaults
    :param password: password to connect with, overriding defaults

    :return:

        Returns a dictionary indicating the status of what has been done. It is structured in
        the following way:

        .. code-block:: python

            {
              'definition': True,
              'cpu': True,
              'mem': True,
              'disks': {'attached': [list of actually attached disks],
                        'detached': [list of actually detached disks]},
              'nics': {'attached': [list of actually attached nics],
                       'detached': [list of actually detached nics]},
              'errors': ['error messages for failures']
            }

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' virt.update domain cpu=2 mem=1024

def update(name,
           cpu=0,
           mem=0,
           disk_profile=None,
           disks=None,
           nic_profile=None,
           interfaces=None,
           graphics=None,
           live=True,
           **kwargs):
    '''
    Update the definition of an existing domain.

    :param name: Name of the domain to update
    :param cpu: Number of virtual CPUs to assign to the virtual machine
    :param mem: Amount of memory to allocate to the virtual machine in MiB.
    :param disk_profile: disk profile to use
    :param disks:
        Disk definitions as documented in the :func:`init` function.
        If neither the profile nor this parameter are defined, the disk devices
        will not be changed. However to clear disks set this parameter to empty list.

    :param nic_profile: network interfaces profile to use
    :param interfaces:
        Network interface definitions as documented in the :func:`init` function.
        If neither the profile nor this parameter are defined, the interface devices
        will not be changed. However to clear network interfaces set this parameter
        to empty list.

    :param graphics:
        The new graphics definition as defined in init-graphics-def_. If not set,
        the graphics will not be changed. To remove a graphics device, set this parameter
        to ``{'type': 'none'}``.

    :param live:
        ``False`` to avoid trying to live update the definition. In such a case, the
        new definition is applied at the next start of the virtual machine. If ``True``,
        not all aspects of the definition can be live updated, but as much as possible
        will be attempted. (Default: ``True``)

    :param connection: libvirt connection URI, overriding defaults
    :param username: username to connect with, overriding defaults
    :param password: password to connect with, overriding defaults

    :return:

        Returns a dictionary indicating the status of what has been done. It is structured in
        the following way:

        .. code-block:: python

            {
              'definition': True,
              'cpu': True,
              'mem': True,
              'disks': {'attached': [list of actually attached disks],
                        'detached': [list of actually detached disks]},
              'nics': {'attached': [list of actually attached nics],
                       'detached': [list of actually detached nics]},
              'errors': ['error messages for failures']
            }

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' virt.update domain cpu=2 mem=1024

    '''
    status = {
        'definition': False,
        'disk': {'attached': [], 'detached': []},
        'interface': {'attached': [], 'detached': []}
    }
    conn = __get_conn(**kwargs)
    domain = _get_domain(conn, name)
    desc = ElementTree.fromstring(domain.XMLDesc(0))
    need_update = False

    # Compute the XML to get the disks, interfaces and graphics
    hypervisor = desc.get('type')
    all_disks = _disk_profile(disk_profile, hypervisor, disks, name, **kwargs)
    new_desc = ElementTree.fromstring(_gen_xml(name,
                                               cpu,
                                               mem,
                                               all_disks,
                                               _get_merged_nics(hypervisor, nic_profile, interfaces),
                                               hypervisor,
                                               domain.OSType(),
                                               desc.find('.//os/type').get('arch'),
                                               graphics,
                                               **kwargs))

    # Update the cpu
    cpu_node = desc.find('vcpu')
    if cpu and int(cpu_node.text) != cpu:
        cpu_node.text = six.text_type(cpu)
        cpu_node.set('current', six.text_type(cpu))
        need_update = True

    # Update the memory, note that libvirt outputs all memory sizes in KiB
    for mem_node_name in ['memory', 'currentMemory']:
        mem_node = desc.find(mem_node_name)
        if mem and int(mem_node.text) != mem * 1024:
            mem_node.text = six.text_type(mem)
            mem_node.set('unit', 'MiB')
            need_update = True

    # Update the XML definition with the new disks and diff changes
    devices_node = desc.find('devices')
    parameters = {'disk': ['disks', 'disk_profile'],
                  'interface': ['interfaces', 'nic_profile'],
                  'graphics': ['graphics']}
    changes = {}
    for dev_type in parameters:
        changes[dev_type] = {}
        func_locals = locals()
        if [param for param in parameters[dev_type] if func_locals.get(param, None) is not None]:
            old = devices_node.findall(dev_type)
            new = new_desc.findall('devices/{0}'.format(dev_type))
            changes[dev_type] = globals()['_diff_{0}_lists'.format(dev_type)](old, new)
            if changes[dev_type]['deleted'] or changes[dev_type]['new']:
                for item in old:
                    devices_node.remove(item)
                devices_node.extend(changes[dev_type]['sorted'])
                need_update = True

    # Set the new definition
    if need_update:
        # Create missing disks if needed
        if changes['disk']:
            for idx, item in enumerate(changes['disk']['sorted']):
                source_file = all_disks[idx]['source_file']
                if item in changes['disk']['new'] and source_file and not os.path.isfile(source_file):
                    _qemu_image_create(all_disks[idx])

        try:
            conn.defineXML(salt.utils.stringutils.to_str(ElementTree.tostring(desc)))
            status['definition'] = True
        except libvirt.libvirtError as err:
            conn.close()
            raise err

        # Do the live changes now that we know the definition has been properly set
        # From that point on, failures are not blocking to try to live update as much
        # as possible.
        commands = []
        if domain.isActive() and live:
            if cpu:
                commands.append({'device': 'cpu',
                                 'cmd': 'setVcpusFlags',
                                 'args': [cpu, libvirt.VIR_DOMAIN_AFFECT_LIVE]})
            if mem:
                commands.append({'device': 'mem',
                                 'cmd': 'setMemoryFlags',
                                 'args': [mem * 1024, libvirt.VIR_DOMAIN_AFFECT_LIVE]})

            for dev_type in ['disk', 'interface']:
                for added in changes[dev_type].get('new', []):
                    commands.append({'device': dev_type,
                                     'cmd': 'attachDevice',
                                     'args': [salt.utils.stringutils.to_str(ElementTree.tostring(added))]})

                for removed in changes[dev_type].get('deleted', []):
                    commands.append({'device': dev_type,
                                     'cmd': 'detachDevice',
                                     'args': [salt.utils.stringutils.to_str(ElementTree.tostring(removed))]})

        for cmd in commands:
            try:
                ret = getattr(domain, cmd['cmd'])(*cmd['args'])
                device_type = cmd['device']
                if device_type in ['cpu', 'mem']:
                    status[device_type] = not bool(ret)
                else:
                    actions = {'attachDevice': 'attached', 'detachDevice': 'detached'}
                    status[device_type][actions[cmd['cmd']]].append(cmd['args'][0])

            except libvirt.libvirtError as err:
                if 'errors' not in status:
                    status['errors'] = []
                status['errors'].append(six.text_type(err))

    conn.close()
    return status