开发网络资源模块

了解网络和安全资源模块

网络和安全设备将配置分成不同的部分(例如接口、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:资源模块构建器放置资源模块和事实模块的文件和目录的目录。

  • 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

使用集合

此示例显示如何在 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 将配置加载到设备并使用 changedcommandsbeforeafter 键生成结果。

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

  • 如果不支持 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 注释上单击“详细信息”。

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

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

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

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

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

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

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

集成测试结构

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

  • 设置 —> 测试 —> 断言 —> 再次测试(用于幂等性) —> 断言 —> 拆除(如果需要) -> 完成。这可以防止测试 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 }}'

请参阅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 和集合

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

开发模块

开始开发模块