开发网络资源模块
了解网络和安全资源模块
网络和安全设备将配置分成不同的部分(例如接口、VLAN 等),这些部分应用于网络或安全服务。Ansible 资源模块利用这一点,允许用户配置设备配置中的子部分或资源。资源模块在不同的网络和安全设备上提供一致的体验。例如,网络资源模块可能只更新网络设备的网络接口、VLAN、ACL 等特定部分的配置。资源模块
获取配置的一部分(事实收集),例如,接口配置。
将返回的配置转换为键值对。
将这些键值对放入内部独立的结构化数据格式中。
现在配置数据已规范化,用户可以更新和修改数据,然后使用资源模块将配置数据发送回设备。这导致了完整的往返配置更新,而无需手动解析、数据操作和数据模型管理。
资源模块有两个顶级键 - config
和 state
config
将资源配置数据模型定义为键值对。config
选项的类型可以是dict
或list of dict
,具体取决于所管理的资源。也就是说,如果设备具有单个全局配置,则它应该是dict
(例如,全局 LLDP 配置)。如果设备有多个配置实例,则它应该是list
类型,列表中的每个元素都是dict
类型(例如,接口配置)。state
定义资源模块在最终设备上执行的操作。
新资源模块的 state
应支持以下值(适用于支持它们的设备)
- merged
Ansible 将设备上的配置与任务中提供的配置合并。
- replaced
Ansible 将设备上的配置子部分替换为任务中提供的配置子部分。
- overridden
Ansible 使用任务中提供的配置覆盖设备上资源的配置。谨慎使用此状态,因为您可能会失去对设备的访问权限(例如,通过覆盖管理接口配置)。
- deleted
Ansible 删除设备上的配置子部分并恢复任何默认设置。
- gathered
Ansible 显示从网络设备收集的资源详细信息,并通过结果中的
gathered
键访问。- rendered
Ansible 以设备原生格式(例如 Cisco IOS CLI)呈现任务中提供的配置。Ansible 将此呈现的配置返回到结果中的
rendered
键中。请注意,此状态不会与网络设备通信,可以在脱机状态下使用。- parsed
Ansible 将
running_configuration
选项中的配置解析为结果中parsed
键中的 Ansible 结构化数据。请注意,这不会从网络设备收集配置,因此此状态可以在脱机状态下使用。
Ansible 维护的集合中的模块必须支持这些状态值。如果您开发的模块仅对状态使用“present”和“absent”,则可以将其提交到社区集合。
注意
状态 rendered
、gathered
和 parsed
不会对设备进行任何更改。
另请参阅
- 网络自动化中 VLAN 资源模块的深入探讨
VLAN 状态值实现方式的演练。
开发网络和安全资源模块
Ansible 工程团队确保 Ansible 维护的集合中的模块设计和代码模式在资源和平台之间保持一致,从而提供与供应商无关的感觉并交付高质量的代码。我们建议您使用 资源模块构建器 开发资源模块。
开发资源模块的高级流程为
在 资源模块模型存储库 中创建和共享资源模型设计,作为 PR 供审查。
下载最新版本的 资源模块构建器。
运行
resource module builder
以根据您已批准的资源模型创建集合脚手架。编写代码以实现您的资源模块。
开发集成和单元测试以验证您的资源模块。
向您要添加新资源模块的相应集合创建 PR。有关确定模块的正确集合的详细信息,请参阅 为 Ansible 维护的集合做贡献。
理解模型和资源模块构建器
资源模块构建器是一个 Ansible Playbook,可帮助开发人员构建和维护 Ansible 资源模块。它使用模型作为模块的唯一真相来源。此模型是一个 yaml
文件,用于模块的 DOCUMENTATION 部分和参数规范。
资源模块构建器具有以下功能
使用定义的模型构建资源模块目录布局和初始类文件。
构建 Ansible 角色或集合。
后续使用资源模块构建器只会替换模块 arspec 和包含模块文档字符串的文件。
允许您将复杂的示例与模型存储在同一目录中。
将模型维护为模块的真相来源,并使用资源模块构建器根据需要更新源文件。
为
<network_os>_<resource>
和<network_os>_facts
生成可用的示例模块。
访问资源模块构建器
要访问资源模块构建器
克隆 GitHub 存储库
git clone https://github.com/ansible-network/resource_module_builder.git
安装需求
pip install -r requirements.txt
创建模型
您必须为您的新资源创建一个模型。该模型是 argspec 和文档字符串的唯一真相来源,使它们保持同步。模型获得批准后,您可以使用资源模块构建器根据模型生成三个项目
新模块的脚手架
新模块的 argspec
新模块的文档字符串
对于功能的任何后续更改,请先更新模型,然后使用资源模块构建器更新模块 argspec 和文档字符串。
例如,资源模型构建器在 models
目录中包含 myos_interfaces.yml
示例,如下所示
---
GENERATOR_VERSION: '1.0'
NETWORK_OS: myos
RESOURCE: interfaces
COPYRIGHT: Copyright 2019 Red Hat
LICENSE: gpl-3.0.txt
DOCUMENTATION: |
module: myos_interfaces
version_added: 1.0.0
short_description: 'Manages <xxxx> attributes of <network_os> <resource>'
description: 'Manages <xxxx> attributes of <network_os> <resource>.'
author: Ansible Network Engineer
notes:
- 'Tested against <network_os> <version>'
options:
config:
description: The provided configuration
type: list
elements: dict
suboptions:
name:
type: str
description: The name of the <resource>
some_string:
type: str
description:
- The some_string_01
choices:
- choice_a
- choice_b
- choice_c
default: choice_a
some_bool:
description:
- The some_bool.
type: bool
some_int:
description:
- The some_int.
type: int
version_added: '1.1.0'
some_dict:
type: dict
description:
- The some_dict.
suboptions:
property_01:
description:
- The property_01
type: str
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- overridden
- deleted
default: merged
EXAMPLES:
- deleted_example_01.txt
- merged_example_01.txt
- overridden_example_01.txt
- replaced_example_01.txt
请注意,您应该为资源支持的每个状态包含示例。资源模块构建器还在示例模型中包含这些示例。
将此模型作为 PR 分享到 资源模块模型存储库 以供审查。您也可以在该位置查看更多模型示例。
从资源模型创建集合脚手架
要使用资源模块构建器从您已批准的资源模型创建集合脚手架
ansible-playbook -e rm_dest=<destination for modules and module utils> \
-e structure=collection \
-e collection_org=<collection_org> \
-e collection_name=<collection_name> \
-e model=<model> \
site.yml
其中参数如下
rm_dest
:资源模块构建器放置资源模块和事实模块的文件和目录的目录。structure
:目录布局类型(角色或集合)role
:生成角色目录布局。collection
:生成集合目录布局。
collection_org
:集合的组织,当 structure=collection 时需要。collection_name
:集合的名称,当 structure=collection 时需要。model
:模型文件的路径。
要使用资源模块构建器创建角色脚手架
ansible-playbook -e rm_dest=<destination for modules and module utils> \
-e structure=role \
-e model=<model> \
site.yml
示例
集合目录布局
此示例显示以下内容的目录布局
network_os
:myosresource
:interfaces
ansible-playbook -e rm_dest=~/github/rm_example \
-e structure=collection \
-e collection_org=cidrblock \
-e collection_name=my_collection \
-e model=models/myos/interfaces/myos_interfaces.yml \
site.yml
├── docs
├── LICENSE.txt
├── playbooks
├── plugins
| ├── action
| ├── filter
| ├── inventory
| ├── modules
| | ├── __init__.py
| | ├── myos_facts.py
| | └── myos_interfaces.py
| └── module_utils
| ├── __init__.py
| └── network
| ├── __init__.py
| └── myos
| ├── argspec
| | ├── facts
| | | ├── facts.py
| | | └── __init__.py
| | ├── __init__.py
| | └── interfaces
| | ├── __init__.py
| | └── interfaces.py
| ├── config
| | ├── __init__.py
| | └── interfaces
| | ├── __init__.py
| | └── interfaces.py
| ├── facts
| | ├── facts.py
| | ├── __init__.py
| | └── interfaces
| | ├── __init__.py
| | └── interfaces.py
| ├── __init__.py
| └── utils
| ├── __init__.py
| └── utils.py
├── README.md
└── roles
角色目录布局
此示例显示以下内容的角色目录布局
network_os
:myosresource
:interfaces
ansible-playbook -e rm_dest=~/github/rm_example/roles/my_role \
-e structure=role \
-e model=models/myos/interfaces/myos_interfaces.yml \
site.yml
roles
└── my_role
├── library
│ ├── __init__.py
│ ├── myos_facts.py
│ └── myos_interfaces.py
├── LICENSE.txt
├── module_utils
│ ├── __init__.py
│ └── network
│ ├── __init__.py
│ └── myos
│ ├── argspec
│ │ ├── facts
│ │ │ ├── facts.py
│ │ │ └── __init__.py
│ │ ├── __init__.py
│ │ └── interfaces
│ │ ├── __init__.py
│ │ └── interfaces.py
│ ├── config
│ │ ├── __init__.py
│ │ └── interfaces
│ │ ├── __init__.py
│ │ └── interfaces.py
│ ├── facts
│ │ ├── facts.py
│ │ ├── __init__.py
│ │ └── interfaces
│ │ ├── __init__.py
│ │ └── interfaces.py
│ ├── __init__.py
│ └── utils
│ ├── __init__.py
│ └── utils.py
└── README.md
使用集合
此示例显示如何在 playbook 中使用生成的集合
---- - hosts: myos101 gather_facts: False tasks: - cidrblock.my_collection.myos_interfaces: register: result - debug: var: result - cidrblock.my_collection.myos_facts: - debug: var: ansible_network_resources
使用角色
此示例显示如何在 playbook 中使用生成的角色
- hosts: myos101
gather_facts: False
roles:
- my_role
- hosts: myos101
gather_facts: False
tasks:
- myos_interfaces:
register: result
- debug:
var: result
- myos_facts:
- debug:
var: ansible_network_resources
资源模块结构和工作流程
资源模块结构包含以下组件
- 模块
library/<ansible_network_os>_<resource>.py
.导入
module_utils
资源包并调用execute_module
API
def main(): result = <resource_package>(module).execute_module()
- 模块 argspec
module_utils/<ansible_network_os>/argspec/<resource>/
.资源的 Argspec。
- 事实
module_utils/<ansible_network_os>/facts/<resource>/
.填充资源的事实。
在
module_utils/<ansible_network_os>/facts/facts.py
中为get_facts
API 创建条目,以使<ansible_network_os>_facts
模块和为每个子集收集的资源模块的事实保持同步。在
module_utils/<ansible_network_os>/facts/facts.py
中的 FACTS_RESOURCE_SUBSETS 列表中添加资源子集条目,以使事实收集工作。
- module_utils 中的模块包
module_utils/<ansible_network_os>/<config>/<resource>/
.实现
execute_module
API,该 API 将配置加载到设备并使用changed
、commands
、before
和after
键生成结果。调用
get_facts
API,该 API 返回<resource>
配置事实,或者如果设备支持 onbox diff,则返回差异。如果不支持 diff,则比较收集的事实和给定的键值。
生成最终配置。
- 实用程序
module_utils/<ansible_network_os>/utils
.用于
<ansible_network_os>
平台的实用程序。
在资源模块上运行 ansible-test sanity
和 tox
在将您的 PR 推送到 Ansible 托管的集合之前,您应该从集合根目录运行 ansible-test sanity
和 tox -elinters
。CI 会运行这两者,如果这些测试失败,则会失败。有关 ansible-test sanity
的详细信息,请参阅 测试 Ansible。
要安装必要的软件包
确保您已配置有效的 Ansible 开发环境。有关详细信息,请参阅 准备用于开发 Ansible 模块的环境。
从集合根目录运行
pip install -r requirements.txt
。
运行
tox -elinters
从集合根目录读取
tox.ini
并安装所需的依赖项(例如black
和flake8
)。使用预配置选项(例如行长和忽略)运行这些选项。
在检查模式下运行
black
以显示哪些文件将被格式化,而不会实际格式化它们。
测试资源模块
测试依赖于由资源模块构建器生成的角色。在对资源模块构建器进行更改后,应重新生成角色并根据需要修改和运行测试。要在更改后生成角色
rm -rf rmb_tests/roles/my_role
ansible-playbook -e rm_dest=./rmb_tests/roles/my_role \
-e structure=role \
-e model=models/myos/interfaces/myos_interfaces.yml \
site.yml
资源模块集成测试
新资源模块的高级集成测试要求如下
为每个状态编写一个测试用例。
编写其他测试用例以测试在给出空
config.yaml
时模块的行为。添加一个往返测试用例。这涉及一个
merge
操作,然后是gather_facts
,一个带有附加配置的merge
更新,然后使用先前收集的事实将state
设置为overridden
来恢复到基本配置。在适用情况下,断言应针对硬编码的事实来源检查前后
dicts
。
我们使用 Zuul 作为 CI 来运行集成测试。
要查看报告,请在 PR 中的 CI 注释上单击“详细信息”。
要查看失败报告,请单击“ansible/check”并选择失败的测试。
要在测试运行时查看日志,请在 Zuul 状态板 中检查您的 PR 编号。
要在本地修复静态测试失败,请运行 tox -e black 在集合的根文件夹内。
要查看 Ansible 运行日志并调试测试失败
单击失败的作业以获取摘要,然后单击“日志”以获取日志。
单击“控制台”并向下滚动以查找失败的测试。
单击失败测试旁边的“>”以获取完整详细信息。
集成测试结构
每个测试用例通常应遵循以下模式
设置 —> 测试 —> 断言 —> 再次测试(用于幂等性) —> 断言 —> 拆除(如果需要) -> 完成。这可以防止测试 playbook 变得庞大且难以排查。
为每个不是断言的任务包含一个名称。您也可以为断言添加名称,但是如果您为每个任务添加名称,则更容易识别失败测试中损坏的任务。
包含测试用例的文件必须以
.yaml
结尾
实现
对于支持 connection: local
*和* connection: network_cli
的平台,请使用以下指南
将
targets/
目录命名为模块名称。main.yaml
文件只需引用传输即可。
以下示例介绍了vyos.vyos 集合中vyos.vyos.vyos_l3_interfaces
模块的集成测试。
test/integration/targets/vyos_l3_interfaces/tasks/main.yaml
---
- import_tasks: cli.yaml
tags:
- cli
test/integration/targets/vyos_l3_interfaces/tasks/cli.yaml
---
- name: collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
register: test_cases
delegate_to: localhost
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test cases (connection=network_cli)
include_tasks:
file: "{{ test_case_to_run }}"
vars:
ansible_connection: network_cli
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
- name: run test case (connection=local)
include_tasks:
file: "{{ test_case_to_run }}"
vars:
ansible_connection: local
ansible_become: false
with_first_found: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
test/integration/targets/vyos_l3_interfaces/tests/cli/overridden.yaml
---
- debug:
msg: START vyos_l3_interfaces merged integration tests on connection={{ ansible_connection
}}
- import_tasks: _remove_config.yaml
- block:
- import_tasks: _populate.yaml
- name: Overrides all device configuration with provided configuration
register: result
vyos.vyos.vyos_l3_interfaces: &id001
config:
- name: eth0
ipv4:
- address: dhcp
- name: eth1
ipv4:
- address: 192.0.2.15/24
state: overridden
- name: Assert that before dicts were correctly generated
assert:
that:
- "{{ populate | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that correct commands were generated
assert:
that:
- "{{ overridden['commands'] | symmetric_difference(result['commands'])\
\ |length == 0 }}"
- name: Assert that after dicts were correctly generated
assert:
that:
- "{{ overridden['after'] | symmetric_difference(result['after']) |length\
\ == 0 }}"
- name: Overrides all device configuration with provided configurations (IDEMPOTENT)
register: result
vyos.vyos.vyos_l3_interfaces: *id001
- name: Assert that the previous task was idempotent
assert:
that:
- result['changed'] == false
- name: Assert that before dicts were correctly generated
assert:
that:
- "{{ overridden['after'] | symmetric_difference(result['before']) |length\
\ == 0 }}"
always:
- import_tasks: _remove_config.yaml
运行时检测测试资源
您的测试应在运行时检测资源(例如接口),而不是将其硬编码到测试中。这允许测试在各种系统上运行。
例如
- name: Collect interface list
connection: ansible.netcommon.network_cli
register: intout
cisco.nxos.nxos_command:
commands:
- show interface brief | json
- set_fact:
intdataraw: "{{ intout.stdout_lines[0]['TABLE_interface']['ROW_interface'] }}"
- set_fact:
nxos_int1: '{{ intdataraw[1].interface }}'
- set_fact:
nxos_int2: '{{ intdataraw[2].interface }}'
- set_fact:
nxos_int3: '{{ intdataraw[3].interface }}'
运行网络集成测试
Ansible 使用 Zuul 在每个 PR 上运行集成测试套件,包括该 PR 引入的新测试。要查找并修复网络模块中的问题,请在提交 PR 之前在本地运行网络集成测试。
首先,创建一个指向测试机器的清单文件。清单组应与平台名称匹配(例如,eos
、ios
)。
cd test/integration
cp inventory.network.template inventory.networking
${EDITOR:-vi} inventory.networking
# Add in machines for the platform(s) you wish to test
要运行这些网络集成测试,请使用ansible-test network-integration --inventory </path/to/inventory> <tests_to_run>
。
ansible-test network-integration --inventory ~/myinventory -vvv vyos_facts
ansible-test network-integration --inventory ~/myinventory -vvv vyos_.*
要运行特定平台的所有网络测试
ansible-test network-integration --inventory /path/to-collection-module/test/integration/inventory.networking vyos_.*
此示例将针对所有vyos
模块运行。请注意,vyos_.*
是正则表达式匹配,而不是 bash 通配符 - 如果您修改此示例,请包含.。
要运行特定模块的集成测试
ansible-test network-integration --inventory /path/to-collection-module/test/integration/inventory.networking vyos_l3_interfaces
要运行特定模块上的单个测试用例
# Only run vyos_l3_interfaces/tests/cli/gathered.yaml
ansible-test network-integration --inventory /path/to-collection-module/test/integration/inventory.networking vyos_l3_interfaces --testcase gathered
要运行特定传输的集成测试
# Only run nxapi test
ansible-test network-integration --inventory /path/to-collection-module/test/integration/inventory.networking --tags="nxapi" nxos_.*
# Skip any cli tests
ansible-test network-integration --inventory /path/to-collection-module/test/integration/inventory.networking --skip-tags="cli" nxos_.*
请参阅test/integration/targets/nxos_bgp/tasks/main.yaml,了解如何在测试中实现此功能。
更多选项
ansible-test network-integration --help
如果您需要其他帮助或反馈,请联系社区。请访问Ansible 通信指南以获取详细信息。
单元测试要求
新资源模块应遵循的高级单元测试要求
为所有状态及其所有可能的配置值组合编写测试用例。
编写测试用例以测试错误条件(负面场景)。
在每个测试用例中检查
changed
和commands
键的值。
我们在我们的 Zuul 测试套件上运行所有单元测试用例,在我们的 CI 设置支持的最新 Python 版本上运行。
使用与集成测试相同的过程来查看 Zuul 单元测试报告和日志。
有关一般单元测试详细信息,请参阅单元模块测试。
示例:单元测试 Ansible 网络资源模块
本节介绍如何为 Ansible 资源模块开发单元测试的示例。
有关模块的 Ansible 单元测试的一般文档,请参阅单元测试和单元测试 Ansible 模块。请先阅读这些页面以了解单元测试以及为什么要以及何时使用它们。
使用模拟对象对 Ansible 网络资源模块进行单元测试
模拟对象在构建特殊或困难情况的单元测试方面非常有用,但它们也可能导致复杂且混乱的编码情况。模拟的一个很好的用途是模拟 API。mock
Python 包与 Ansible 捆绑在一起(使用import units.compat.mock
)。
您可以模拟设备连接和来自设备的输出,如下所示
self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config"
)
self.get_config = self.mock_get_config.start()
self.mock_load_config = patch(
"ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config"
)
self.load_config = self.mock_load_config.start()
self.mock_get_resource_connection_config = patch(
"ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection"
)
self.get_resource_connection_config = (self.mock_get_resource_connection_config.start())
self.mock_get_resource_connection_facts = patch(
"ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection"
)
self.get_resource_connection_facts = (self.mock_get_resource_connection_facts.start())
self.mock_edit_config = patch(
"ansible_collections.arista.eos.plugins.module_utils.network.eos.providers.providers.CliProvider.edit_config"
)
self.edit_config = self.mock_edit_config.start()
self.mock_execute_show_command = patch(
"ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.l2_interfaces.l2_interfaces.L2_interfacesFacts.get_device_data"
)
self.execute_show_command = self.mock_execute_show_command.start()
模块的事实文件现在包含一个新方法get_device_data
。在此处调用get_device_data
以模拟设备输出。
模拟设备数据
要模拟从设备获取结果或提供来自外部库的其他复杂数据结构,您可以使用fixtures
读取预生成的数据。这些预生成数据的文本文件位于test/units/modules/network/PLATFORM/fixtures/
中。例如,请参阅eos_l2_interfaces.cfg 文件。
使用load_fixture
方法加载数据,并将此数据设置为事实文件中get_device_data
方法的返回值。
def load_fixtures(self, commands=None, transport='cli'):
def load_from_file(*args, **kwargs):
return load_fixture('eos_l2_interfaces_config.cfg')
self.execute_show_command.side_effect = load_from_file
有关实际示例,请参阅单元测试文件test_eos_l2_interfaces。
另请参阅
- 单元测试
深入探讨为 Ansible 模块开发单元测试
- 测试 Ansible 和集合
在本地运行测试,包括收集和报告覆盖率数据
- 开发模块
开始开发模块