开发网络资源模块

理解网络和安全资源模块

网络和安全设备将配置分为多个部分(例如接口、VLAN 等),这些部分适用于网络或安全服务。Ansible 资源模块利用这一点,允许用户配置设备配置中的子部分或资源。资源模块为不同的网络和安全设备提供一致的体验。例如,网络资源模块可能仅更新网络设备的特定部分(例如网络接口、VLAN、ACL 等)的配置。资源模块

  1. 获取一部分配置(事实收集),例如接口配置。

  2. 将返回的配置转换为键值对。

  3. 将这些键值对放入内部独立的结构化数据格式中。

现在配置数据已标准化,用户可以更新和修改数据,然后使用资源模块将配置数据发送回设备。这导致完整的往返配置更新,而无需手动解析、数据操作和数据模型管理。

资源模块有两个顶级键 - configstate

  • config 将资源配置数据模型定义为键值对。config 选项的类型可以是 dictlist 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”,您可以将其提交给社区集合。

注意

状态 renderedgatheredparsed 不会对设备执行任何更改。

另请参阅

网络自动化 VLAN 资源模块的深入分析

演练如何为 VLAN 实现状态值。

开发网络和安全资源模块

Ansible 工程团队确保 Ansible 维护的集合中的模块设计和代码模式在资源和平台之间是统一的,以提供与供应商无关的感觉并交付高质量的代码。我们建议您使用 资源模块构建器 来开发资源模块。

开发资源模块的高级流程是

  1. 资源模块模型存储库 中创建并共享资源模型设计,作为供审查的 PR。

  2. 下载最新版本的 资源模块构建器

  3. 运行 resource module builder,从您批准的资源模型创建集合脚手架。

  4. 编写代码以实现您的资源模块。

  5. 开发集成和单元测试以验证您的资源模块。

  6. 创建 PR 到您要添加新资源模块的相应集合。有关确定模块正确集合的详细信息,请参阅 为 Ansible 维护的集合做贡献

理解模型和资源模块构建器

资源模块构建器是一个 Ansible Playbook,可帮助开发人员搭建和维护 Ansible 资源模块。它使用模型作为模块的单一事实来源。此模型是一个 yaml 文件,用于模块的 DOCUMENTATION 部分和参数规范。

资源模块构建器具有以下功能

  • 使用定义的模型来搭建资源模块目录布局和初始类文件。

  • 搭建 Ansible 角色或集合。

  • 后续使用资源模块构建器只会替换模块 arspec 和包含模块文档字符串的文件。

  • 允许您将复杂的示例与模型一起存储在同一目录中。

  • 维护模型作为模块的事实来源,并使用资源模块构建器根据需要更新源文件。

  • <network_os>_<resource><network_os>_facts 生成工作示例模块。

访问资源模块构建器

要访问资源模块构建器

  1. 克隆 GitHub 存储库

git clone https://github.com/ansible-network/resource_module_builder.git
  1. 安装要求

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:资源模块构建器放置资源模块和 facts 模块的文件和目录的目录。

  • 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:myos

  • resource:接口

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:myos

  • resource:接口

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()
模块参数规范
  • module_utils/<ansible_network_os>/argspec/<resource>/.

  • 资源的参数规范。

Facts
  • module_utils/<ansible_network_os>/facts/<resource>/.

  • 填充资源的 facts。

  • module_utils/<ansible_network_os>/facts/facts.py 中为 get_facts API 添加条目,以保持 <ansible_network_os>_facts 模块和为每个子集收集的资源模块的 facts 同步。

  • module_utils/<ansible_network_os>/facts/facts.py 中,将资源子集条目添加到 FACTS_RESOURCE_SUBSETS 列表中,以使 facts 收集工作正常。

module_utils 中的模块包
  • module_utils/<ansible_network_os>/<config>/<resource>/.

  • 实现 execute_module API,该 API 将配置加载到设备并使用 changedcommandsbeforeafter 键生成结果。

  • 调用 get_facts API,该 API 返回 <resource> 配置 facts,如果设备支持 onbox diff,则返回差异。

  • 如果不支持 diff,则比较收集的 facts 和给定的键值。

  • 生成最终配置。

工具
  • module_utils/<ansible_network_os>/utils.

  • 用于 <ansible_network_os> 平台的实用工具。

在资源模块上运行 ansible-test sanitytox

在将 PR 推送到 Ansible 维护的集合之前,您应该从集合根目录运行 ansible-test sanitytox -elinters。CI 会运行这两个测试,如果这些测试失败,则会失败。有关 ansible-test sanity 的详细信息,请参阅 测试 Ansible

要安装必要的包

  1. 确保您配置了有效的 Ansible 开发环境。有关详细信息,请参阅 为开发 Ansible 模块准备环境

  2. 从集合根目录运行 pip install -r requirements.txt

运行 tox -elinters

  • 从集合根目录读取 tox.ini 并安装所需的依赖项(例如 blackflake8)。

  • 使用预配置的选项(例如行长度和忽略)运行这些依赖项。

  • 在检查模式下运行 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

资源模块集成测试

新资源模块的高级集成测试要求如下

  1. 为每个状态编写一个测试用例。

  2. 编写额外的测试用例,以测试在给定空 config.yaml 时模块的行为。

  3. 添加一个往返测试用例。这包括 merge 操作,然后是 gather_facts、使用其他配置的 merge 更新,然后使用之前收集的 facts 将 state 设置为 overridden 来恢复到基本配置。

  4. 在适用的情况下,断言应检查硬编码的真值来源的 after 和 before dicts

我们使用 Zuul 作为 CI 来运行集成测试。

  • 要查看报告,请单击 PR 中 CI 注释中的 详细信息

  • 要查看失败报告,请单击 ansible/check 并选择失败的测试。

  • 要在测试运行时查看日志,请在 Zuul 状态板 中检查您的 PR 编号。

  • 要在本地修复静态测试失败,请在集合的根文件夹内运行 tox -e black

要查看 Ansible 运行日志并调试测试失败

  1. 单击失败的作业以获取摘要,然后单击 日志 以获取日志。

  2. 单击 控制台 并向下滚动以查找失败的测试。

  3. 单击失败测试旁边的 > 以获取完整详细信息。

集成测试结构

每个测试用例通常应遵循此模式

  • 设置 —> 测试 —> 断言 —> 再次测试(以进行幂等性)—> 断言 —> 拆卸(如果需要)—> 完成。这可以防止测试 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 之前在本地运行网络集成测试。

首先,创建一个指向您的测试机器的清单文件。清单组应与平台名称匹配(例如,eosios)。

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 通信指南 获取详细信息。

单元测试要求

新资源模块应遵循的高级单元测试要求

  1. 为所有状态以及配置值的所有可能组合编写测试用例。

  2. 编写测试用例来测试错误条件(负面场景)。

  3. 检查每个测试用例中 changedcommands 键的值。

我们在 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 和集合

在本地运行测试,包括收集和报告覆盖率数据

开发模块

开始开发模块