根据一些过滤器从字典中选择 3 个元素

Pick 3 elements from dictionary according to some filters

我有一个词典列表:

"my_dict": [
  {"disk": "disk1", "size": 600, "controller": "sc1"},
  {"disk": "disk2", "size": 700, "controller": "sc2"},
  {"disk": "disk3", "size": 600, "controller": "sc1"},
  {"disk": "disk4", "size": 500, "controller": "sc3"},
  ...
  ...
]

这个列表中有很多元素,我需要选择其中的 3 个来制作带备用的 raid1 磁盘。

选择 3 个磁盘的限制条件:

如何过滤元素和 select 3 个具有上述限制的磁盘?我已经根据磁盘的大小和控制器使用 sort() 过滤器对磁盘进行了排序。但这并不能保证前 3 个磁盘大小相同并且具有相同的控制器。

谢谢。

这不是最易读的解决方案,但它有效:

---
- hosts: localhost
  gather_facts: false
  vars:
    my_dict:
      - {"disk": "disk1", "size": 600, "controller": "sc1"}
      - {"disk": "disk2", "size": 700, "controller": "sc2"}
      - {"disk": "disk3", "size": 600, "controller": "sc1"}
      - {"disk": "disk4", "size": 500, "controller": "sc3"}
      - {"disk": "disk5", "size": 600, "controller": "sc1"}
      - {"disk": "disk6", "size": 700, "controller": "sc1"}
  tasks:
    - debug:
        msg: "{{ (my_dict_candidates[0] | default([]))[0:3] | default([]) }}"
      vars:
        my_dict_candidates: "{{ my_dict_grouped | map('length') | zip(my_dict_grouped) | selectattr('0', 'gt', 2) | map(attribute='1') }}"
        my_dict_grouped: "{{ my_dict | groupby('controller') | map(attribute='1') | map('groupby', 'size') | flatten(levels=1) | map(attribute='1') }}"
TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": [
        {
            "controller": "sc1",
            "disk": "disk1",
            "size": 600
        },
        {
            "controller": "sc1",
            "disk": "disk3",
            "size": 600
        },
        {
            "controller": "sc1",
            "disk": "disk5",
            "size": 600
        }
    ]
}

这是一个足够复杂的逻辑,您最好编写一个实现它的自定义过滤器而不是尝试使用纯 Jinja,但 Jinja 解决方案是可行的。

my_dict_grouped 中,我们基本上是 groupby() | map(attribute='1') 两次;该地图将元组 return 从 groupby() 编辑为一个更简单的列表。 此步骤将通过支持同时按多个事物分组的过滤器来简化,但我不知道有一个。

my_dict_candidates中我们计算出my_dict_grouped中每个列表的长度,然后select只计算满足您长度要求的列表。请注意,这会生成一个列表,该列表可能包含 0 到多个结果,具体取决于输入数据。这就是为什么我们必须在最终表达式中使用空列表的默认值,我们只保留第一个顶部列表(即最小的可用磁盘)和 return 仅前三个结果用于您的 raid 阵列。

作为我在评论中的建议的说明,这里有一个自定义过滤器的示例,它比您的要求稍微更进一步。它会让你 select n 个磁盘(默认为 3),磁盘的最小大小(默认为 0,即先前数量的磁盘可用的最小磁盘)。请注意,如果没有磁盘符合条件,过滤器 returns 一个空列表。

您可以阅读 documentation for custom filters 如果您需要与团队共享,您将在其中了解有关如何开发和分发它们的更多信息。对于此示例,我使用了最直接的方法并将过滤器添加到与我的测试手册相邻的 filter_plugins/ 目录中,该目录结构如下

 $ tree
.
├── filter_plugins
│   └── raid_disk_filters.py
└── playbook.yml

这是 filter_plugins/raid_disk_filters.py 文件。它定义了一个名为 smallest_disks_by_controller

的过滤器
from itertools import groupby


def smallest_disks_by_controller(disk_data, array_size=3, min_disk_size=0):
    """
    Returns a list of disk datas for the smallest disks on the same controller
    :arg disk_data: the list of dicts containing the disk data. each element
      is expected to contain 3 keys: disk, size and controller)
    :arg array_size the number of similar disks to return in the result
    :arg min_disk_size minimum size of the disk to select, smallest one by default
    """

    candidates = []
    keyfunc = lambda x: (x['size'], x['controller'])
    for key, group in groupby(sorted(disk_data, key=keyfunc), keyfunc):
        current_size = key[0]
        current_disks = list(group)
        if current_size >= min_disk_size and len(current_disks) >= array_size:
            candidates = current_disks[:array_size]
            break
    return candidates


class FilterModule(object):
    """Filters to work with disks for raid array creation"""

    def filters(self):
        """Return the filter list."""
        return {
            'smallest_disks_by_controller': smallest_disks_by_controller
        }

这是测试playbook.yml

---
- name: Custom filter demo
  hosts: localhost
  gather_facts: false

  vars:
    "my_disks": [{ "disk": "disk1", "size": 900, "controller": "sc1" },{ "disk": "disk2", "size": 700, "controller": "sc2" },{ "disk": "disk3", "size": 600, "controller": "sc1" },{ "disk": "disk4", "size": 500, "controller": "sc3" },{ "disk": "disk5", "size": 600, "controller": "sc1" },{ "disk": "disk6", "size": 700, "controller": "sc2" },{ "disk": "disk7", "size": 600, "controller": "sc1" },{ "disk": "disk8", "size": 500, "controller": "sc3" },{ "disk": "disk9", "size": 600, "controller": "sc1" },{ "disk": "disk10", "size": 700, "controller": "sc2" },{ "disk": "disk11", "size": 600, "controller": "sc1" },{ "disk": "disk12", "size": 500, "controller": "sc3" },{ "disk": "disk13", "size": 600, "controller": "sc1" },{ "disk": "disk14", "size": 700, "controller": "sc2" },{ "disk": "disk15", "size": 600, "controller": "sc1" },{ "disk": "disk16", "size": 500, "controller": "sc3" }]

  tasks:
    - name: Get a list of matching disks
      vars:
        msg: |-
          3 disks, min size: {{ my_disks | smallest_disks_by_controller }}
          3 disks, size >= 700: {{ my_disks | smallest_disks_by_controller(3, 700) }}
          4 disks, min size: {{ my_disks | smallest_disks_by_controller(4) }}
          4 disks, size >=600: {{ my_disks | smallest_disks_by_controller(4, 600) }}
          7 disks, size >=1000: {{ my_disks | smallest_disks_by_controller(7, 1000) }}
      debug:
        msg: "{{ msg.split('\n') }}"

给出:

$ ansible-playbook playbook.yml 

PLAY [Custom filter demo] ***************************************************

TASK [Get a list of matching disks] *****************************************
ok: [localhost] => {
    "msg": [
        "3 disks, min size: [{'disk': 'disk4', 'size': 500, 'controller': 'sc3'}, {'disk': 'disk8', 'size': 500, 'controller': 'sc3'}, {'disk': 'disk12', 'size': 500, 'controller': 'sc3'}]",
        "3 disks, size >= 700: [{'disk': 'disk2', 'size': 700, 'controller': 'sc2'}, {'disk': 'disk6', 'size': 700, 'controller': 'sc2'}, {'disk': 'disk10', 'size': 700, 'controller': 'sc2'}]",
        "4 disks, min size: [{'disk': 'disk4', 'size': 500, 'controller': 'sc3'}, {'disk': 'disk8', 'size': 500, 'controller': 'sc3'}, {'disk': 'disk12', 'size': 500, 'controller': 'sc3'}, {'disk': 'disk16', 'size': 500, 'controller': 'sc3'}]",
        "4 disks, size >=600: [{'disk': 'disk3', 'size': 600, 'controller': 'sc1'}, {'disk': 'disk5', 'size': 600, 'controller': 'sc1'}, {'disk': 'disk7', 'size': 600, 'controller': 'sc1'}, {'disk': 'disk9', 'size': 600, 'controller': 'sc1'}]",
        "7 disks, size >=1000: []"
    ]
}

PLAY RECAP ******************************************************************
localhost: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0