开发网络资源模块

了解网络和安全资源模块

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

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

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

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

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

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

  • config 将资源配置数据模型定义为键值对。 config 选项的类型可以是 dictlist of dict,具体取决于所管理的资源。也就是说,如果设备具有单个全局配置,则它应该是一个 dict(例如,全局 LLDP 配置)。如果设备具有多个配置实例,则它应该为 list 类型,列表中的每个元素都为 dict 类型(例如,接口配置)。

  • state 定义资源模块对最终设备执行的操作。

新的资源模块的 state 应该支持以下值(适用于支持它们的设备):

合并

Ansible 将设备上的配置与任务中提供的配置合并。

替换

Ansible 将设备上的配置子部分替换为任务中提供的配置子部分。

覆盖

Ansible 使用任务中提供的配置覆盖设备上资源的配置。使用此状态时要谨慎,因为您可能会丢失对设备的访问权限(例如,通过覆盖管理接口配置)。

已删除

Ansible 删除设备上的配置子部分并恢复所有默认设置。

收集

Ansible 显示从网络设备收集的资源详细信息,并通过结果中的 gathered 键进行访问。

渲染

Ansible 以设备原生格式(例如 Cisco IOS CLI)呈现任务中提供的配置。Ansible 将此呈现的配置返回结果中的 rendered 键。请注意,此状态不会与网络设备通信,可以离线使用。

解析

Ansible 将配置从 running_configuration 选项解析为 Ansible 结构化数据,并在结果中的 parsed 键中显示。请注意,这不会从网络设备收集配置,因此此状态可以离线使用。

Ansible 维护的集合中的模块必须支持这些状态值。如果您开发的模块的状态只有“present”和“absent”,则可以将其提交到社区集合。

注意

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

另请参阅

深入探讨用于网络自动化的 VLAN 资源模块

VLAN 状态值的实现方法概述。

开发网络和安全资源模块

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

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

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

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

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

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

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

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

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

资源模块构建器是一个 Ansible 剧本,可帮助开发人员构建和维护 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 和 docstring 的唯一真相来源,确保它们保持同步。模型获得批准后,您可以使用资源模块构建器根据模型生成三个项目:

  • 新模块的脚手架

  • 新模块的 argspec

  • 新模块的 docstring

对于任何后续的功能更改,请先更新模型,然后使用资源模块构建器更新模块的 argspec 和 docstring。

例如,资源模型构建器在 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:myos

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

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

使用集合

此示例显示了如何在剧本中使用生成的集合:

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

使用角色

此示例显示了如何在剧本中使用生成的集合:

- 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 将配置加载到设备并使用 changedcommandsbeforeafter 键生成结果。

  • 调用 get_facts API,该 API 返回 <resource> 配置事实,或在设备支持 onbox diff 时返回差异。

  • 比较收集的事实和给定的键值,如果不支持差异。

  • 生成最终配置。

工具
  • 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 更新,然后使用先前收集的事实(将 state 设置为 overridden)恢复到基本配置。

  4. 在适用情况下,断言应针对硬编码的事实来源检查 dicts 在之后的和之前的操作。

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

  • 要查看报告,请在 PR 中的 CI 评论中单击 Details

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

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

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

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

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

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

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

集成测试结构

每个测试用例通常应遵循以下模式:

  • setup —> test —> assert —> test again (for idempotency) —> assert —> tear down (if needed) -> done。这可以防止测试剧本变得过于庞大,难以调试。

  • 为每个不是断言的任务包含一个名称。您也可以为断言添加名称,但如果您为每个任务添加一个名称,则更容易识别失败测试中的损坏任务。

  • 包含测试用例的文件必须以 .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 }}'

请参阅 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。Ansible 中捆绑了 mock Python 包(使用 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 和集合

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

开发模块

开始开发模块