mirror of
https://github.com/linux-system-roles/network.git
synced 2026-01-23 02:15:17 +00:00
Support the nmstate network state configuration
The users want to apply the nmstate network state configuration to the interface directly through the role, which necessitates the less complexity of the network configuration and allows the partial configuration on the network. To warrant that the users are capable to apply the nmstate network state configuration, add the support for the `network_state` variable. Signed-off-by: Wen Liang <liangwen12year@gmail.com>
This commit is contained in:
parent
6dfd6485ed
commit
e694ad72c1
7 changed files with 535 additions and 10 deletions
119
README.md
119
README.md
|
|
@ -43,18 +43,25 @@ For each host a list of networking profiles can be configured via the
|
|||
- For `nm`, profiles correspond to connection profiles as handled by
|
||||
NetworkManager.
|
||||
|
||||
Note that the `network` role primarily operates on networking profiles
|
||||
(connections) and not on devices, but it uses the profile name by default as
|
||||
the interface name. It is also possible to create generic profiles, by creating
|
||||
for example a profile with a certain IP configuration without activating the
|
||||
profile. To apply the configuration to the actual networking interface, use the
|
||||
`nmcli` commands on the target system.
|
||||
For each host the network state configuration can also be applied to the interface
|
||||
directly via the `network_state` variable, and only the `nm` provider supports using
|
||||
the `network_state` variable.
|
||||
|
||||
Note that the `network` role both operates on the connection profiles of the devices
|
||||
(via the `network_connections` variable) and on devices directly (via the
|
||||
`network_state` variable). When configuring the connection profiles through the role,
|
||||
it uses the profile name by default as the interface name. It is also possible to
|
||||
create generic profiles, by creating for example a profile with a certain IP
|
||||
configuration without activating the profile. To apply the configuration to the actual
|
||||
networking interface, use the `nmcli` commands on the target system.
|
||||
|
||||
**Warning**: The `network` role updates or creates all connection profiles on
|
||||
the target system as specified in the `network_connections` variable. Therefore,
|
||||
the `network` role removes options from the specified profiles if the options are
|
||||
only present on the system but not in the `network_connections` variable.
|
||||
Exceptions are mentioned below.
|
||||
Exceptions are mentioned below. However, the partial networking configuration can be
|
||||
achieved via specifying the network state configuration in the `network_state`
|
||||
variable.
|
||||
|
||||
Variables
|
||||
---------
|
||||
|
|
@ -77,6 +84,9 @@ the name prefix. List of variables:
|
|||
NetworkManager-wifi is not installed, NetworkManager must be restarted prior
|
||||
to the connection being configured. Setting this to `no` will prevent the
|
||||
role from restarting network service.
|
||||
- `network_state` - The network state settings can be configured in the managed
|
||||
host, and the format and the syntax of the configuration should be consistent
|
||||
with the [nmstate state examples](https://nmstate.io/examples.html) (YAML).
|
||||
|
||||
Examples of Variables
|
||||
---------------------
|
||||
|
|
@ -91,6 +101,20 @@ network_connections:
|
|||
network_allow_restart: yes
|
||||
```
|
||||
|
||||
```yaml
|
||||
network_provider: nm
|
||||
network_state:
|
||||
interfaces:
|
||||
- name: eth0
|
||||
#...
|
||||
routes:
|
||||
config:
|
||||
#...
|
||||
dns-resolver:
|
||||
config:
|
||||
#...
|
||||
```
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
|
|
@ -1173,6 +1197,87 @@ network_connections:
|
|||
key_mgmt: "owe"
|
||||
```
|
||||
|
||||
Examples of Applying the Network State Configuration
|
||||
-------------------
|
||||
|
||||
Configuring the IP addresses:
|
||||
|
||||
```yaml
|
||||
network_state:
|
||||
interfaces:
|
||||
- name: ethtest0
|
||||
type: ethernet
|
||||
state: up
|
||||
ipv4:
|
||||
enabled: true
|
||||
address:
|
||||
- ip: 192.168.122.250
|
||||
prefix-length: 24
|
||||
dhcp: false
|
||||
ipv6:
|
||||
enabled: true
|
||||
address:
|
||||
- ip: 2001:db8::1:1
|
||||
prefix-length: 64
|
||||
autoconf: false
|
||||
dhcp: false
|
||||
- name: ethtest1
|
||||
type: ethernet
|
||||
state: up
|
||||
ipv4:
|
||||
enabled: true
|
||||
address:
|
||||
- ip: 192.168.100.192
|
||||
prefix-length: 24
|
||||
auto-dns: false
|
||||
dhcp: false
|
||||
ipv6:
|
||||
enabled: true
|
||||
address:
|
||||
- ip: 2001:db8::2:1
|
||||
prefix-length: 64
|
||||
autoconf: false
|
||||
dhcp: false
|
||||
```
|
||||
|
||||
Configuring the route:
|
||||
|
||||
```yaml
|
||||
network_state:
|
||||
interfaces:
|
||||
- name: eth1
|
||||
type: ethernet
|
||||
state: up
|
||||
ipv4:
|
||||
enabled: true
|
||||
address:
|
||||
- ip: 192.0.2.251
|
||||
prefix-length: 24
|
||||
dhcp: false
|
||||
|
||||
routes:
|
||||
config:
|
||||
- destination: 198.51.100.0/24
|
||||
metric: 150
|
||||
next-hop-address: 192.0.2.251
|
||||
next-hop-interface: eth1
|
||||
table-id: 254
|
||||
```
|
||||
|
||||
Configuring the DNS search and server:
|
||||
|
||||
```yaml
|
||||
network_state:
|
||||
dns-resolver:
|
||||
config:
|
||||
search:
|
||||
- example.com
|
||||
- example.org
|
||||
server:
|
||||
- 2001:4860:4860::8888
|
||||
- 8.8.8.8
|
||||
```
|
||||
|
||||
### Invalid and Wrong Configuration
|
||||
|
||||
The `network` role rejects invalid configurations. It is recommended to test the role
|
||||
|
|
|
|||
51
examples/network_state.yml
Normal file
51
examples/network_state.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
network_state:
|
||||
interfaces:
|
||||
- name: ethtest0
|
||||
type: ethernet
|
||||
state: up
|
||||
ipv4:
|
||||
enabled: true
|
||||
address:
|
||||
- ip: 192.168.122.250
|
||||
prefix-length: 24
|
||||
dhcp: false
|
||||
ipv6:
|
||||
enabled: true
|
||||
address:
|
||||
- ip: 2001:db8::1:1
|
||||
prefix-length: 64
|
||||
autoconf: false
|
||||
dhcp: false
|
||||
- name: ethtest1
|
||||
type: ethernet
|
||||
state: up
|
||||
ipv4:
|
||||
enabled: true
|
||||
auto-dns: false
|
||||
dhcp: true
|
||||
ipv6:
|
||||
enabled: true
|
||||
auto-dns: false
|
||||
dhcp: true
|
||||
routes:
|
||||
config:
|
||||
- destination: 192.0.2.100/30
|
||||
metric: 150
|
||||
next-hop-address: 192.168.122.250
|
||||
next-hop-interface: ethtest0
|
||||
table-id: 254
|
||||
dns-resolver:
|
||||
config:
|
||||
search:
|
||||
- example.com
|
||||
- example.org
|
||||
server:
|
||||
- 2001:4860:4860::8888
|
||||
- 8.8.8.8
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
...
|
||||
74
library/network_state.py
Normal file
74
library/network_state.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: network_state
|
||||
version_added: "2.9"
|
||||
short_description: module for network role to apply network state configuration
|
||||
description:
|
||||
- This module allows to apply the network state configuration through nmstate,
|
||||
https://github.com/nmstate/nmstate
|
||||
options:
|
||||
desired_state:
|
||||
description: Nmstate state definition
|
||||
required: true
|
||||
type: dict
|
||||
author: "Wen Liang (@liangwen12year)"
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
state:
|
||||
description: Network state after running the module
|
||||
type: dict
|
||||
returned: always
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import libnmstate # pylint: disable=import-error
|
||||
|
||||
|
||||
class NetworkState:
|
||||
def __init__(self, module, module_name):
|
||||
self.module = module
|
||||
self.params = module.params
|
||||
self.result = dict(changed=False)
|
||||
self.module_name = module_name
|
||||
self.previous_state = libnmstate.show()
|
||||
|
||||
def run(self):
|
||||
desired_state = self.params["desired_state"]
|
||||
libnmstate.apply(desired_state)
|
||||
current_state = libnmstate.show()
|
||||
if current_state != self.previous_state:
|
||||
self.result["changed"] = True
|
||||
|
||||
self.result["state"] = current_state
|
||||
|
||||
self.module.exit_json(**self.result)
|
||||
|
||||
|
||||
def run_module():
|
||||
module_args = dict(
|
||||
desired_state=dict(type="dict", required=True),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
)
|
||||
|
||||
network_state_module = NetworkState(module, "network_state")
|
||||
network_state_module.run()
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -8,6 +8,22 @@
|
|||
debug:
|
||||
msg: "Using network provider: {{ network_provider }}"
|
||||
|
||||
- name: Abort applying the network state configuration if using the
|
||||
`network_state` variable with the initscripts provider
|
||||
fail:
|
||||
msg: Only the `nm` provider supports using the `network_state` variable
|
||||
when:
|
||||
- network_state is defined
|
||||
- network_provider == "initscripts"
|
||||
|
||||
- name: Abort applying the network state configuration if the system version
|
||||
of the managed host is below 8
|
||||
fail:
|
||||
msg: The `network_state` variable uses nmstate backend which is only
|
||||
supported since RHEL-8
|
||||
when:
|
||||
- network_state is defined
|
||||
- ansible_distribution_major_version | int < 8
|
||||
# Depending on the plugins, checking installed packages might be slow
|
||||
# for example subscription manager might slow this down
|
||||
# Therefore install packages only when rpm does not find them
|
||||
|
|
@ -19,6 +35,31 @@
|
|||
- not network_packages is subset(ansible_facts.packages.keys())
|
||||
register: __network_package_install
|
||||
|
||||
- name: Install NetworkManager and nmstate when using network_state variable
|
||||
package:
|
||||
name:
|
||||
- NetworkManager
|
||||
- nmstate
|
||||
state: present
|
||||
when:
|
||||
- network_state is defined
|
||||
- ansible_distribution == 'Fedora' and
|
||||
ansible_distribution_major_version | int > 27 or
|
||||
ansible_distribution != 'Fedora' and
|
||||
ansible_distribution_major_version | int > 7
|
||||
|
||||
- name: Install python3-libnmstate when using network_state variable
|
||||
package:
|
||||
name:
|
||||
- python3-libnmstate
|
||||
state: present
|
||||
when:
|
||||
- network_state is defined
|
||||
- ansible_distribution == 'Fedora' and
|
||||
ansible_distribution_major_version | int > 34 or
|
||||
ansible_distribution != 'Fedora' and
|
||||
ansible_distribution_major_version | int > 8
|
||||
|
||||
# If network packages changed and wireless or team connections are specified,
|
||||
# NetworkManager must be restarted
|
||||
- name: Restart NetworkManager due to wireless or team interfaces
|
||||
|
|
@ -41,7 +82,7 @@
|
|||
state: started
|
||||
enabled: true
|
||||
when:
|
||||
- network_provider == "nm"
|
||||
- network_provider == "nm" or network_state is defined
|
||||
no_log: true
|
||||
|
||||
# If any 802.1x connections are used, the wpa_supplicant
|
||||
|
|
@ -84,14 +125,26 @@
|
|||
__lsr_ansible_managed: "{{ lookup('template', 'get_ansible_managed.j2') }}"
|
||||
register: __network_connections_result
|
||||
|
||||
- name: Show stderr messages
|
||||
- name: Configure networking state
|
||||
network_state:
|
||||
desired_state: "{{ network_state | default([]) }}"
|
||||
register: __network_state_result
|
||||
when: network_state is defined
|
||||
|
||||
- name: Show stderr messages for the network_connections
|
||||
debug:
|
||||
var: __network_connections_result.stderr_lines
|
||||
|
||||
- name: Show debug messages
|
||||
- name: Show debug messages for the network_connections
|
||||
debug:
|
||||
var: __network_connections_result
|
||||
verbosity: 1
|
||||
|
||||
- name: Show debug messages for the network_state
|
||||
debug:
|
||||
var: __network_state_result
|
||||
verbosity: 1
|
||||
when: network_state is defined
|
||||
|
||||
- name: Re-test connectivity
|
||||
ping:
|
||||
|
|
|
|||
|
|
@ -83,6 +83,9 @@ ibution_major_version | int < 9",
|
|||
MINIMUM_VERSION: "'1.26.0'",
|
||||
"comment": "# NetworkManager 1.26.0 added support for match.path setting",
|
||||
},
|
||||
"playbooks/tests_network_state.yml": {
|
||||
EXTRA_RUN_CONDITION: "ansible_distribution_major_version | int > 7",
|
||||
},
|
||||
"playbooks/tests_reapply.yml": {},
|
||||
"playbooks/tests_route_table.yml": {},
|
||||
"playbooks/tests_routing_rules.yml": {},
|
||||
|
|
|
|||
218
tests/playbooks/tests_network_state.yml
Normal file
218
tests/playbooks/tests_network_state.yml
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
|
||||
- name: Test configuring ethernet devices
|
||||
hosts: all
|
||||
vars:
|
||||
type: veth
|
||||
interface0: ethtest0
|
||||
interface1: ethtest1
|
||||
|
||||
|
||||
tasks:
|
||||
- name: "set type={{ type }} and interface={{ interface0 }}"
|
||||
set_fact:
|
||||
type: "{{ type }}"
|
||||
interface: "{{ interface0 }}"
|
||||
- include_tasks: tasks/show_interfaces.yml
|
||||
- include_tasks: tasks/manage_test_interface.yml
|
||||
vars:
|
||||
state: present
|
||||
- include_tasks: tasks/assert_device_present.yml
|
||||
- name: "set type={{ type }} and interface={{ interface1 }}"
|
||||
set_fact:
|
||||
type: "{{ type }}"
|
||||
interface: "{{ interface1 }}"
|
||||
- include_tasks: tasks/show_interfaces.yml
|
||||
- include_tasks: tasks/manage_test_interface.yml
|
||||
vars:
|
||||
state: present
|
||||
- include_tasks: tasks/assert_device_present.yml
|
||||
|
||||
|
||||
- name: Configure the IP addresses
|
||||
import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_state:
|
||||
interfaces:
|
||||
- name: ethtest0
|
||||
type: ethernet
|
||||
state: up
|
||||
ipv4:
|
||||
enabled: true
|
||||
address:
|
||||
- ip: 192.168.122.250
|
||||
prefix-length: 24
|
||||
dhcp: false
|
||||
ipv6:
|
||||
enabled: true
|
||||
address:
|
||||
- ip: 2001:db8::1:1
|
||||
prefix-length: 64
|
||||
autoconf: false
|
||||
dhcp: false
|
||||
- name: ethtest1
|
||||
type: ethernet
|
||||
state: up
|
||||
ipv4:
|
||||
enabled: true
|
||||
auto-dns: false
|
||||
address:
|
||||
- ip: 192.168.122.88
|
||||
prefix-length: 24
|
||||
dhcp: false
|
||||
ipv6:
|
||||
enabled: true
|
||||
auto-dns: false
|
||||
dhcp: true
|
||||
|
||||
- name: Get the ethtest0 state configuration
|
||||
command: nmstatectl show ethtest0
|
||||
register: ethtest0_state
|
||||
ignore_errors: yes # noqa ignore-errors
|
||||
changed_when: false
|
||||
|
||||
- name: Get the ethtest1 state configuration
|
||||
command: nmstatectl show ethtest1
|
||||
register: ethtest1_state
|
||||
ignore_errors: yes # noqa ignore-errors
|
||||
changed_when: false
|
||||
|
||||
- name: Assert that the ethtest0 state configuration contains the specified
|
||||
settings
|
||||
assert:
|
||||
that:
|
||||
- ethtest0_state.stdout is search("192.168.122.250")
|
||||
- ethtest0_state.stdout is search("2001:db8::1:1")
|
||||
msg: the ethtest0 state configuration does not contain the specified
|
||||
settings
|
||||
|
||||
- name: Assert that the ethtest1 state configuration contains the specified
|
||||
settings
|
||||
assert:
|
||||
that:
|
||||
- ethtest1_state.stdout is search("192.168.122.88")
|
||||
msg: the ethtest1 state configuration does not contain the specified
|
||||
settings
|
||||
|
||||
- name: Configure the route
|
||||
import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_state:
|
||||
routes:
|
||||
config:
|
||||
- destination: 192.0.2.100/30
|
||||
metric: 150
|
||||
next-hop-address: 192.168.122.250
|
||||
next-hop-interface: ethtest0
|
||||
table-id: 254
|
||||
|
||||
- name: Get the route configuration
|
||||
command: nmstatectl show
|
||||
register: route
|
||||
ignore_errors: yes # noqa ignore-errors
|
||||
changed_when: false
|
||||
|
||||
- name: Assert that the route configuration contains the specified route
|
||||
assert:
|
||||
that:
|
||||
- route.stdout is search("destination:(\s+)192.0.2.100/30")
|
||||
- route.stdout is search("metric:(\s+)150")
|
||||
- route.stdout is search("next-hop-address:(\s+)192.168.122.250")
|
||||
- route.stdout is search("next-hop-interface:(\s+)ethtest0")
|
||||
- route.stdout is search("table-id:(\s+)254")
|
||||
msg: the route configuration does not contain the specified route
|
||||
|
||||
- name: Set the DNS processing mode and the resolv.conf management mode
|
||||
lineinfile:
|
||||
path: /etc/NetworkManager/NetworkManager.conf
|
||||
line: "rc-manager=unmanaged\ndns=systemd-resolved"
|
||||
insertafter: \[main\]
|
||||
|
||||
- name: Restart the NetworkManager
|
||||
service:
|
||||
name: NetworkManager
|
||||
state: restarted
|
||||
|
||||
- name: Install the systemd-resolved
|
||||
package:
|
||||
name: systemd-resolved
|
||||
state: present
|
||||
when:
|
||||
- ansible_distribution_major_version | int > 8
|
||||
|
||||
- name: Enable the systemd-resolved service
|
||||
service:
|
||||
name: systemd-resolved
|
||||
enabled: true
|
||||
|
||||
- name: Configure the DNS
|
||||
import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_state:
|
||||
dns-resolver:
|
||||
config:
|
||||
search:
|
||||
- example.com
|
||||
- example.org
|
||||
server:
|
||||
- 2001:4860:4860::8888
|
||||
|
||||
- name: Get the DNS configuration from nmstatectl
|
||||
command: nmstatectl show
|
||||
register: nmstatectl
|
||||
ignore_errors: yes # noqa ignore-errors
|
||||
changed_when: false
|
||||
|
||||
- name: Get the DNS configuration from the file `/etc/resolv.conf`
|
||||
command: cat /etc/resolv.conf
|
||||
register: resolvconf
|
||||
ignore_errors: yes # noqa ignore-errors
|
||||
changed_when: false
|
||||
|
||||
- name: Check if `/etc/resolv.conf` is generated by NM
|
||||
command: grep "Generated by NetworkManager" /etc/resolv.conf
|
||||
register: generateByNM
|
||||
ignore_errors: yes # noqa ignore-errors
|
||||
changed_when: false
|
||||
|
||||
- name: Assert that the nmstatectl contains the specified DNS configuration
|
||||
assert:
|
||||
that:
|
||||
- nmstatectl.stdout is search("example.com")
|
||||
- nmstatectl.stdout is search("example.org")
|
||||
- nmstatectl.stdout is search("2001:4860:4860::8888")
|
||||
msg: the nmstatectl does not contain the specified DNS configuration
|
||||
|
||||
- name: Assert that the file `/etc/resolv.conf` does not contain the
|
||||
specified DNS configuration
|
||||
assert:
|
||||
that:
|
||||
- resolvconf.stdout is not search("example.com") and
|
||||
resolvconf.stdout is not search("example.org") and
|
||||
resolvconf.stdout is not search("2001:4860:4860::8888") or
|
||||
generateByNM.stdout | length == 0
|
||||
msg: the file `/etc/resolv.conf` contains the specified DNS
|
||||
configuration
|
||||
|
||||
- command: resolvectl
|
||||
name: "** TEST check resolvectl"
|
||||
register: result
|
||||
until: "'example.com' in result.stdout"
|
||||
retries: 20
|
||||
delay: 2
|
||||
changed_when: false
|
||||
|
||||
- include_tasks: tasks/delete_interface.yml
|
||||
- include_tasks: tasks/assert_device_absent.yml
|
||||
- name: "set interface={{ interface0 }}"
|
||||
set_fact:
|
||||
type: "{{ type }}"
|
||||
interface: "{{ interface0 }}"
|
||||
- include_tasks: tasks/delete_interface.yml
|
||||
- include_tasks: tasks/assert_device_absent.yml
|
||||
...
|
||||
21
tests/tests_network_state_nm.yml
Normal file
21
tests/tests_network_state_nm.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# This file was generated by ensure_provider_tests.py
|
||||
---
|
||||
# set network provider and gather facts
|
||||
- hosts: all
|
||||
name: Run playbook 'playbooks/tests_network_state.yml' with nm as provider
|
||||
tasks:
|
||||
- include_tasks: tasks/el_repo_setup.yml
|
||||
- name: Set network provider to 'nm'
|
||||
set_fact:
|
||||
network_provider: nm
|
||||
tags:
|
||||
- always
|
||||
|
||||
|
||||
# The test requires or should run with NetworkManager, therefore it cannot run
|
||||
# on RHEL/CentOS 6
|
||||
- import_playbook: playbooks/tests_network_state.yml
|
||||
when:
|
||||
- ansible_distribution_major_version != '6'
|
||||
- ansible_distribution_major_version | int > 7
|
||||
Loading…
Add table
Add a link
Reference in a new issue