开发网络资源模块
了解网络和安全资源模块
网络和安全设备将配置分为应用于网络或安全服务的各个部分(例如接口、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
选项中的配置解析为 Ansible 结构化数据,位于结果中的parsed
键中。请注意,这不会从网络设备收集配置,因此此状态可以在离线时使用。
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 差异,则返回差异。如果不支持差异,则比较收集的事实和给定的键值。
生成最终配置。
- 实用程序
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
还原回基本配置。在适用的情况下,断言应根据硬编码的真实来源检查 after 和 before
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_l3_interfaces
模块在 vyos.vyos 集合中的集成测试。
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 }}'
有关此内容的完整测试示例,请参阅 https://github.com/ansible-collections/cisco.nxos/blob/main/tests/integration/targets/prepare_nxos_tests/tasks/main.yml。
运行网络集成测试
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 和集合
在本地运行测试,包括收集和报告覆盖率数据
- 开发模块
开始开发模块