Syncs the given config and devices with the object
        (a profile or a container)
        returns a changes dict with all changes made.

        obj :
            The object to sync with / or just test with.

        newconfig:
            The new config to check with the obj.

        newdevices:
            The new devices to check with the obj.

        test:
            Wherever to not change anything and give "Would change" message.

def sync_config_devices(obj, newconfig, newdevices, test=False):
    ''' Syncs the given config and devices with the object
        (a profile or a container)
        returns a changes dict with all changes made.

        obj :
            The object to sync with / or just test with.

        newconfig:
            The new config to check with the obj.

        newdevices:
            The new devices to check with the obj.

        test:
            Wherever to not change anything and give "Would change" message.
    '''
    changes = {}

    #
    # config changes
    #
    if newconfig is None:
        newconfig = {}

    newconfig = dict(list(zip(
        map(six.text_type, newconfig.keys()),
        map(six.text_type, newconfig.values())
    )))
    cck = set(newconfig.keys())

    obj.config = dict(list(zip(
        map(six.text_type, obj.config.keys()),
        map(six.text_type, obj.config.values())
    )))
    ock = set(obj.config.keys())

    config_changes = {}
    # Removed keys
    for k in ock.difference(cck):
        # Ignore LXD internals.
        if k.startswith('volatile.') or k.startswith('image.'):
            continue

        if not test:
            config_changes[k] = (
                'Removed config key "{0}", its value was "{1}"'
            ).format(k, obj.config[k])
            del obj.config[k]
        else:
            config_changes[k] = (
                'Would remove config key "{0} with value "{1}"'
            ).format(k, obj.config[k])

    # same keys
    for k in cck.intersection(ock):
        # Ignore LXD internals.
        if k.startswith('volatile.') or k.startswith('image.'):
            continue

        if newconfig[k] != obj.config[k]:
            if not test:
                config_changes[k] = (
                    'Changed config key "{0}" to "{1}", '
                    'its value was "{2}"'
                ).format(k, newconfig[k], obj.config[k])
                obj.config[k] = newconfig[k]
            else:
                config_changes[k] = (
                    'Would change config key "{0}" to "{1}", '
                    'its current value is "{2}"'
                ).format(k, newconfig[k], obj.config[k])

    # New keys
    for k in cck.difference(ock):
        # Ignore LXD internals.
        if k.startswith('volatile.') or k.startswith('image.'):
            continue

        if not test:
            config_changes[k] = (
                'Added config key "{0}" = "{1}"'
            ).format(k, newconfig[k])
            obj.config[k] = newconfig[k]
        else:
            config_changes[k] = (
                'Would add config key "{0}" = "{1}"'
            ).format(k, newconfig[k])

    if config_changes:
        changes['config'] = config_changes

    #
    # devices changes
    #
    if newdevices is None:
        newdevices = {}

    dk = set(obj.devices.keys())
    ndk = set(newdevices.keys())

    devices_changes = {}
    # Removed devices
    for k in dk.difference(ndk):
        # Ignore LXD internals.
        if k == u'root':
            continue

        if not test:
            devices_changes[k] = (
                'Removed device "{0}"'
            ).format(k)
            del obj.devices[k]
        else:
            devices_changes[k] = (
                'Would remove device "{0}"'
            ).format(k)

    # Changed devices
    for k, v in six.iteritems(obj.devices):
        # Ignore LXD internals also for new devices.
        if k == u'root':
            continue

        if k not in newdevices:
            # In test mode we don't delete devices above.
            continue

        if newdevices[k] != v:
            if not test:
                devices_changes[k] = (
                    'Changed device "{0}"'
                ).format(k)
                obj.devices[k] = newdevices[k]
            else:
                devices_changes[k] = (
                    'Would change device "{0}"'
                ).format(k)

    # New devices
    for k in ndk.difference(dk):
        # Ignore LXD internals.
        if k == u'root':
            continue

        if not test:
            devices_changes[k] = (
                'Added device "{0}"'
            ).format(k)
            obj.devices[k] = newdevices[k]
        else:
            devices_changes[k] = (
                'Would add device "{0}"'
            ).format(k)

    if devices_changes:
        changes['devices'] = devices_changes

    return changes