根据一些过滤器从字典中选择 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
我有一个词典列表:
"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