WIP3
This commit is contained in:
11
roles/linux-system-roles.network/.gitignore
vendored
Normal file
11
roles/linux-system-roles.network/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/.cache
|
||||
/.coverage
|
||||
*.pyc
|
||||
/.pytest_cache
|
||||
/tests/.coverage
|
||||
/tests/htmlcov*
|
||||
/tests/__pycache__/
|
||||
/tests/remote-coveragedata-*
|
||||
/tests/tmp_merge_coveragerc
|
||||
/tests/total-*coveragedata
|
||||
/.tox
|
||||
27
roles/linux-system-roles.network/.travis.yml
Normal file
27
roles/linux-system-roles.network/.travis.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
dist: xenial
|
||||
language: python
|
||||
matrix:
|
||||
include:
|
||||
- python: 2.6
|
||||
dist: trusty
|
||||
- python: 2.7
|
||||
- python: 3.5
|
||||
env: aptpkgs=python3-selinux
|
||||
- python: 3.6
|
||||
- python: 3.7
|
||||
- python: 3.7-dev
|
||||
- python: 3.8-dev
|
||||
# - python: nightly
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- if [ -n "${aptpkgs}" ]; then sudo apt-get install -y python3-selinux; fi
|
||||
|
||||
install:
|
||||
- pip install tox tox-travis
|
||||
|
||||
script:
|
||||
- tox
|
||||
28
roles/linux-system-roles.network/LICENSE
Normal file
28
roles/linux-system-roles.network/LICENSE
Normal file
@@ -0,0 +1,28 @@
|
||||
BSD-3-Clause License
|
||||
|
||||
Copyright (c) 2017-2018 Red Hat, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
682
roles/linux-system-roles.network/README.md
Normal file
682
roles/linux-system-roles.network/README.md
Normal file
@@ -0,0 +1,682 @@
|
||||
linux-system-roles/network
|
||||
==========================
|
||||
[](https://coveralls.io/github/linux-system-roles/network)
|
||||
[](https://travis-ci.org/linux-system-roles/network)
|
||||
[](https://github.com/ambv/black)
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The `network` role enables users to configure network on the target machines.
|
||||
This role can be used to configure:
|
||||
|
||||
- Ethernet interfaces
|
||||
- Bridge interfaces
|
||||
- Bonded interfaces
|
||||
- VLAN interfaces
|
||||
- MacVLAN interfaces
|
||||
- Infiniband interfaces
|
||||
- IP configuration
|
||||
|
||||
Introduction
|
||||
------------
|
||||
The `network` role supports two providers: `nm` and `initscripts`. `nm` is
|
||||
used by default in RHEL7 and `initscripts` in RHEL6. These providers can be
|
||||
configured per host via the [`network_provider`](#provider) variable. In
|
||||
absence of explicit configuration, it is autodetected based on the
|
||||
distribution. However, note that either `nm` or `initscripts` is not tied to a certain
|
||||
distribution. The `network` role works everywhere the required API is available.
|
||||
This means that `nm` requires at least NetworkManager's API version 1.2 available.
|
||||
For `initscripts`, the legacy network service is required as used in Fedora or RHEL.
|
||||
|
||||
For each host a list of networking profiles can be configured via the
|
||||
`network_connections` variable.
|
||||
|
||||
- For `initscripts`, profiles correspond to ifcfg files in the `/etc/sysconfig/network-scripts/ifcfg-*` directory.
|
||||
|
||||
- For `NetworkManager`, profiles correspond to connection profiles as handled by
|
||||
NetworkManager. Fedora and RHEL use the `ifcfg-rh-plugin` for NetworkManager,
|
||||
which also writes or reads configuration files to `/etc/sysconfig/network-scripts/ifcfg-*`
|
||||
for compatibility.
|
||||
|
||||
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.
|
||||
|
||||
**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.
|
||||
|
||||
Variables
|
||||
---------
|
||||
The `network` role is configured via variables starting with `network_` as the name prefix.
|
||||
List of variables:
|
||||
|
||||
* `network_provider` - The `network_provider` variable allows to set a specific
|
||||
provider (`nm` or `initscripts`) . Setting it to `{{ network_provider_os_default }}`,
|
||||
the provider is set depending on the operating system. This is usually `nm`
|
||||
except for RHEL 6 or CentOS 6 systems.
|
||||
|
||||
* `network_connections` - The connection profiles are configured as `network_connections`,
|
||||
which is a list of dictionaries that include specific options.
|
||||
|
||||
|
||||
Examples of Variables
|
||||
---------------------
|
||||
|
||||
Setting the variables
|
||||
|
||||
```yaml
|
||||
network_provider: nm
|
||||
network_connections:
|
||||
- name: eth0
|
||||
#...
|
||||
```
|
||||
|
||||
Options
|
||||
-------
|
||||
The `network_connections` variable is a list of dictionaries that include the following options.
|
||||
List of options:
|
||||
|
||||
### `name` (required)
|
||||
|
||||
The `name` option identifies the connection profile. It is not the name of the
|
||||
networking interface for which the profile applies, though we can associate
|
||||
the profile with an interface and give them the same name.
|
||||
Note that you can have multiple profiles for the same device, but only
|
||||
one profile can be active on the device each time.
|
||||
For NetworkManager, a connection can only be active at one device each time.
|
||||
|
||||
* For `NetworkManager`, the `name` option corresponds to the
|
||||
[`connection.id`](https://developer.gnome.org/NetworkManager/stable/nm-settings.html#nm-settings.property.connection.id)
|
||||
property option.
|
||||
Although NetworkManager supports multiple connections with the same `connection.id`,
|
||||
the `network` role cannot handle a duplicate `name`. Specifying a `name` multiple
|
||||
times refers to the same connection profile.
|
||||
|
||||
* For `initscripts`, the `name` option determines the ifcfg file name `/etc/sysconfig/network-scripts/ifcfg-$NAME`.
|
||||
Note that the `name` does not specify the `DEVICE` but a filename. As a consequence,
|
||||
`'/'` is not a valid character for the `name`.
|
||||
|
||||
You can also use the same connection profile multiple times. Therefore, it is possible to create a profile and activate it separately.
|
||||
|
||||
### `state`
|
||||
|
||||
The `state` option identifies what is the runtime state of each connection profile. The `state` option (optional) can be set to the following values:
|
||||
|
||||
* `up` - the connection profile is activated
|
||||
* `down` - the connection profile is deactivated
|
||||
|
||||
#### `state: up`
|
||||
- For `NetworkManager`, this corresponds to `nmcli connection id {{name}} up`.
|
||||
|
||||
- For `initscripts`, this corresponds to `ifup {{name}}`.
|
||||
|
||||
When the `state` option is set to `up`, you can also specify the `wait` option (optional):
|
||||
|
||||
* `wait: 0` - initiates only the activation, but does not wait until the device is fully connected.
|
||||
The connection will be completed in the background, for example after a DHCP lease was received.
|
||||
* `wait: <seconds>` is a timeout that enables you to decide how long you give the device to
|
||||
activate. The default is using a suitable timeout. Note that the `wait` option is
|
||||
only supported by NetworkManager.
|
||||
|
||||
Note that `state: up` always re-activates the profile and possibly changes the
|
||||
networking configuration, even if the profile was already active before. As
|
||||
a consequence, `state: up` always changes the system.
|
||||
|
||||
#### `state: down`
|
||||
|
||||
- For `NetworkManager`, it corresponds to `nmcli connection id {{name}} down`.
|
||||
|
||||
- For `initscripts`, it corresponds to call `ifdown {{name}}`.
|
||||
|
||||
You can deactivate a connection profile, even if is currently not active. As a consequence, `state: down` always changes the system.
|
||||
|
||||
Note that if the `state` option is unset, the connection profile’s runtime state will not be changed.
|
||||
|
||||
|
||||
### `persistent_state`
|
||||
|
||||
The `persistent_state` option identifies if a connection profile is persistent (saved on disk). The `persistent_state` option can be set to the following values:
|
||||
|
||||
#### `persistent_state: present` (default)
|
||||
|
||||
Note that if `persistent_state` is `present` and the connection profile contains
|
||||
the `type` option, the profile will be created or updated. If the connection profile is
|
||||
incomplete (no `type` option), the behavior is undefined. Also, the `present` value
|
||||
does not directly result in a change in the network configuration. If the `state` option
|
||||
is not set to `up`, the profile is only created or modified, not activated.
|
||||
|
||||
For NetworkManager, the new connection profile is created with the `autoconnect`
|
||||
option enabled by default. Therefore, NetworkManager can activate the new
|
||||
profile on a currently disconnected device. ([rh#1401515](https://bugzilla.redhat.com/show_bug.cgi?id=1401515)).
|
||||
|
||||
#### `persistent_state: absent`
|
||||
|
||||
The `absent` value ensures that the profile is not present on the
|
||||
target host. If a profile with the given `name` exists, it will be deleted. In this case:
|
||||
|
||||
- `NetworkManager` deletes all connection profiles with the corresponding `connection.id`.
|
||||
Deleting a profile usually does not change the current networking configuration, unless
|
||||
the profile was currently activated on a device. Deleting the currently
|
||||
active connection profile disconnects the device. That makes the device eligible
|
||||
to autoconnect another connection (for more details, see [rh#1401515](https://bugzilla.redhat.com/show_bug.cgi?id=1401515)).
|
||||
|
||||
- `initscripts` deletes the ifcfg file in most cases with no impact on the runtime state of the system unless some component is watching the sysconfig directory.
|
||||
|
||||
**Note**: For profiles that only contain a `state` option, the `network` role only activates
|
||||
or deactivates the connection without changing its configuration.
|
||||
|
||||
|
||||
### `type`
|
||||
|
||||
The `type` option can be set to the following values:
|
||||
|
||||
- `ethernet`
|
||||
- `bridge`
|
||||
- `bond`
|
||||
- `team`
|
||||
- `vlan`
|
||||
- `macvlan`
|
||||
- `infiniband`
|
||||
|
||||
#### `type: ethernet`
|
||||
|
||||
If the type is `ethernet`, then there can be an extra `ethernet` dictionary with the following
|
||||
items (options): `autoneg`, `speed` and `duplex`, which correspond to the
|
||||
settings of the `ethtool` utility with the same name.
|
||||
|
||||
* `autoneg`: `yes` (default) or `no` [if auto-negotiation is enabled or disabled]
|
||||
* `speed`: speed in Mbit/s
|
||||
* `duplex`: `half` or `full`
|
||||
|
||||
Note that the `speed` and `duplex` link settings are required when autonegotiation is disabled (autoneg:no).
|
||||
|
||||
#### `type: bridge`, `type: bond`, `type: team`
|
||||
|
||||
The `bridge`, `bond`, `team` device types work similar. Note that `team` is not supported in RHEL6 kernels.
|
||||
|
||||
For slaves, the `slave_type` and `master` properties must be set. Note that slaves should not have `ip` settings.
|
||||
|
||||
The `master` refers to the `name` of a profile in the Ansible
|
||||
playbook. It is neither an interface-name nor a connection-id of
|
||||
NetworkManager.
|
||||
|
||||
- For NetworkManager, `master` will be converted to the `connection.uuid`
|
||||
of the corresponding profile.
|
||||
|
||||
- For initscripts, the master is looked up as the `DEVICE` from the corresponding
|
||||
ifcfg file.
|
||||
|
||||
As `master` refers to other profiles of the same or another play,
|
||||
the order of the `connections` list matters. Also, `--check` ignores
|
||||
the value of the `master` and assumes it will be present during a real
|
||||
run. That means, in presence of an invalid `master`, `--check` may
|
||||
signal success but the actual play run fails.
|
||||
|
||||
#### `type: vlan`
|
||||
|
||||
Similar to `master`, the `parent` references the connection profile in the ansible
|
||||
role.
|
||||
|
||||
#### `type: macvlan`
|
||||
|
||||
Similar to `master` and `vlan`, the `parent` references the connection profile in the ansible
|
||||
role.
|
||||
|
||||
|
||||
### `autoconnect`
|
||||
|
||||
By default, profiles are created with autoconnect enabled.
|
||||
|
||||
- For `NetworkManager`, this corresponds to the `connection.autoconnect` property.
|
||||
|
||||
- For `initscripts`, this corresponds to the `ONBOOT` property.
|
||||
|
||||
### `mac`
|
||||
|
||||
The `mac` address is optional and restricts the profile to be usable only on
|
||||
devices with the given MAC address. `mac` is only allowed for `type`
|
||||
`ethernet` or `infiniband` to match a non-virtual device with the
|
||||
profile.
|
||||
|
||||
- For `NetworkManager`, `mac` is the permanent MAC address, `ethernet.mac-address`.
|
||||
|
||||
- For `initscripts`, `mac` is the currently configured MAC address of the device (`HWADDR`).
|
||||
|
||||
### `interface_name`
|
||||
|
||||
For the `ethernet` and `infiniband` types, the `interface_name` option restricts the profile to
|
||||
the given interface by name. This argument is optional and by default the
|
||||
profile name is used unless a mac address is specified using the `mac` key.
|
||||
Specifying an empty string (`""`) means that the profile is not
|
||||
restricted to a network interface.
|
||||
|
||||
**Note:** With [persistent interface naming](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Networking_Guide/ch-Consistent_Network_Device_Naming.html),
|
||||
the interface is predictable based on the hardware configuration.
|
||||
Otherwise, the `mac` address might be an option.
|
||||
|
||||
For virtual interface types such as bridges, the `interface_name` is the name of the created
|
||||
interface. In case of a missing `interface_name`, the `name` of the profile name is used.
|
||||
|
||||
**Note:** The `name` (the profile name) and the `interface_name` (the device name) may be
|
||||
different or the profile may not be tied to an interface at all.
|
||||
|
||||
### `zone`
|
||||
|
||||
The `zone` option sets the firewalld zone for the interface.
|
||||
|
||||
Slaves to the bridge, bond or team devices cannot specify a zone.
|
||||
|
||||
|
||||
### `ip`
|
||||
|
||||
The IP configuration supports the following options:
|
||||
|
||||
* `address`
|
||||
|
||||
Manual addressing can be specified via a list of addresses under the `address` option.
|
||||
|
||||
* `dhcp4` and `auto6`
|
||||
|
||||
Also, manual addressing can be specified by setting either `dhcp4` or `auto6`.
|
||||
The `dhcp4` key is for DHCPv4 and `auto6` for StateLess Address Auto Configuration
|
||||
(SLAAC). Note that the `dhcp4` and `auto6` keys can be omitted and the default key
|
||||
depends on the presence of manual addresses.
|
||||
|
||||
|
||||
* `dhcp4_send_hostname`
|
||||
|
||||
If `dhcp4` is enabled, it can be configured whether the DHCPv4 request includes
|
||||
the hostname via the `dhcp4_send_hostname` option. Note that `dhcp4_send_hostname`
|
||||
is only supported by the `nm` provider and corresponds to
|
||||
[`ipv4.dhcp-send-hostname`](https://developer.gnome.org/NetworkManager/stable/nm-settings.html#nm-settings.property.ipv4.dhcp-send-hostname)
|
||||
property.
|
||||
|
||||
* `dns` and `dns_search`
|
||||
|
||||
Manual DNS configuration can be specified via a list of addresses
|
||||
given in the `dns` option and a list of domains to search given in the
|
||||
`dns_search` option.
|
||||
|
||||
|
||||
* `route_metric4` and `route_metric6`
|
||||
|
||||
- For `NetworkManager`, `route_metric4` and `route_metric6` corresponds to the
|
||||
[`ipv4.route-metric`](https://developer.gnome.org/NetworkManager/stable/nm-settings.html#nm-settings.property.ipv4.route-metric) and
|
||||
[`ipv6.route-metric`](https://developer.gnome.org/NetworkManager/stable/nm-settings.html#nm-settings.property.ipv6.route-metric)
|
||||
properties, respectively. If specified, it determines the route metric for DHCP
|
||||
assigned routes and the default route, and thus the priority for multiple interfaces.
|
||||
|
||||
* `route`
|
||||
|
||||
Static route configuration can be specified via a list of routes given in the `route`
|
||||
option. The default value is an empty list. Each route is a dictionary with the following
|
||||
entries: `network`, `prefix`, `gateway` and `metric`. `network` and `prefix` specify
|
||||
the destination network.
|
||||
Note that Classless inter-domain routing (CIDR) notation or network mask notation are not supported yet.
|
||||
|
||||
* `route_append_only`
|
||||
|
||||
The `route_append_only` option allows only to add new routes to the
|
||||
existing routes on the system.
|
||||
|
||||
If the `route_append_only` boolean option is set to `yes`, the specified routes are appended to the existing routes.
|
||||
If `route_append_only` is set to `no` (default), the current routes are replaced.
|
||||
Note that setting `route_append_only` to `yes` without setting `route` has the effect of preserving the current static routes.
|
||||
|
||||
* `rule_append_only`
|
||||
|
||||
The `rule_append_only` boolean option allows to preserve the current routing rules.
|
||||
Note that specifying routing rules is not supported yet.
|
||||
|
||||
**Note:** When `route_append_only` or `rule_append_only` is not specified, the `network` role deletes the current routes or routing rules.
|
||||
|
||||
**Note:** Slaves to the bridge, bond or team devices cannot specify `ip` settings.
|
||||
|
||||
### `ethtool`
|
||||
|
||||
The ethtool settings allow to enable or disable varios features. The names
|
||||
correspond to the names used by the `ethtool` utility. Depending on the actual
|
||||
kernel and device, changing some features might not be supported.
|
||||
|
||||
```yaml
|
||||
ethtool:
|
||||
features:
|
||||
esp-hw-offload: yes|no # optional
|
||||
esp-tx-csum-hw-offload: yes|no # optional
|
||||
fcoe-mtu: yes|no # optional
|
||||
gro: yes|no # optional
|
||||
gso: yes|no # optional
|
||||
highdma: yes|no # optional
|
||||
hw-tc-offload: yes|no # optional
|
||||
l2-fwd-offload: yes|no # optional
|
||||
loopback: yes|no # optional
|
||||
lro: yes|no # optional
|
||||
ntuple: yes|no # optional
|
||||
rx: yes|no # optional
|
||||
rx-all: yes|no # optional
|
||||
rx-fcs: yes|no # optional
|
||||
rx-gro-hw: yes|no # optional
|
||||
rx-udp_tunnel-port-offload: yes|no # optional
|
||||
rx-vlan-filter: yes|no # optional
|
||||
rx-vlan-stag-filter: yes|no # optional
|
||||
rx-vlan-stag-hw-parse: yes|no # optional
|
||||
rxhash: yes|no # optional
|
||||
rxvlan: yes|no # optional
|
||||
sg: yes|no # optional
|
||||
tls-hw-record: yes|no # optional
|
||||
tls-hw-tx-offload: yes|no # optional
|
||||
tso: yes|no # optional
|
||||
tx: yes|no # optional
|
||||
tx-checksum-fcoe-crc: yes|no # optional
|
||||
tx-checksum-ip-generic: yes|no # optional
|
||||
tx-checksum-ipv4: yes|no # optional
|
||||
tx-checksum-ipv6: yes|no # optional
|
||||
tx-checksum-sctp: yes|no # optional
|
||||
tx-esp-segmentation: yes|no # optional
|
||||
tx-fcoe-segmentation: yes|no # optional
|
||||
tx-gre-csum-segmentation: yes|no # optional
|
||||
tx-gre-segmentation: yes|no # optional
|
||||
tx-gso-partial: yes|no # optional
|
||||
tx-gso-robust: yes|no # optional
|
||||
tx-ipxip4-segmentation: yes|no # optional
|
||||
tx-ipxip6-segmentation: yes|no # optional
|
||||
tx-nocache-copy: yes|no # optional
|
||||
tx-scatter-gather: yes|no # optional
|
||||
tx-scatter-gather-fraglist: yes|no # optional
|
||||
tx-sctp-segmentation: yes|no # optional
|
||||
tx-tcp-ecn-segmentation: yes|no # optional
|
||||
tx-tcp-mangleid-segmentation: yes|no # optional
|
||||
tx-tcp-segmentation: yes|no # optional
|
||||
tx-tcp6-segmentation: yes|no # optional
|
||||
tx-udp-segmentation: yes|no # optional
|
||||
tx-udp_tnl-csum-segmentation: yes|no # optional
|
||||
tx-udp_tnl-segmentation: yes|no # optional
|
||||
tx-vlan-stag-hw-insert: yes|no # optional
|
||||
txvlan: yes|no # optional
|
||||
```
|
||||
|
||||
Examples of Options
|
||||
-------------------
|
||||
|
||||
Setting the same connection profile multiple times:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: Wired0
|
||||
type: ethernet
|
||||
interface_name: eth0
|
||||
ip:
|
||||
dhcp4: yes
|
||||
|
||||
- name: Wired0
|
||||
state: up
|
||||
```
|
||||
|
||||
Activating a preexisting connection profile:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: eth0
|
||||
state: up
|
||||
```
|
||||
|
||||
Deactivating a preexisting connection profile:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: eth0
|
||||
state: down
|
||||
```
|
||||
|
||||
Creating a persistent connection profile:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: eth0
|
||||
#persistent_state: present # default
|
||||
type: ethernet
|
||||
autoconnect: yes
|
||||
mac: 00:00:5e:00:53:5d
|
||||
ip:
|
||||
dhcp4: yes
|
||||
```
|
||||
|
||||
Deleting a connection profile named `eth0` (if it exists):
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: eth0
|
||||
persistent_state: absent
|
||||
```
|
||||
|
||||
Configuring the Ethernet link settings:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: eth0
|
||||
type: ethernet
|
||||
|
||||
ethernet:
|
||||
autoneg: no
|
||||
speed: 1000
|
||||
duplex: full
|
||||
```
|
||||
|
||||
Creating a bridge connection:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: br0
|
||||
type: bridge
|
||||
#interface_name: br0 # defaults to the connection name
|
||||
```
|
||||
|
||||
|
||||
Configuring a bridge connection:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: internal-br0
|
||||
interface_name: br0
|
||||
type: bridge
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
```
|
||||
|
||||
Setting `master` and `slave_type`:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: br0-bond0
|
||||
type: bond
|
||||
interface_name: bond0
|
||||
master: internal-br0
|
||||
slave_type: bridge
|
||||
|
||||
- name: br0-bond0-eth1
|
||||
type: ethernet
|
||||
interface_name: eth1
|
||||
master: br0-bond0
|
||||
slave_type: bond
|
||||
```
|
||||
|
||||
Configuring VLANs:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: eth1-profile
|
||||
autoconnet: no
|
||||
type: ethernet
|
||||
interface_name: eth1
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
|
||||
- name: eth1.6
|
||||
autoconnect: no
|
||||
type: vlan
|
||||
parent: eth1-profile
|
||||
vlan:
|
||||
id: 6
|
||||
ip:
|
||||
address:
|
||||
- 192.0.2.5/24
|
||||
auto6: no
|
||||
```
|
||||
|
||||
Configuring MACVLAN:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: eth0-profile
|
||||
type: ethernet
|
||||
interface_name: eth0
|
||||
ip:
|
||||
address:
|
||||
- 192.168.0.1/24
|
||||
|
||||
- name: veth0
|
||||
type: macvlan
|
||||
parent: eth0-profile
|
||||
macvlan:
|
||||
mode: bridge
|
||||
promiscuous: yes
|
||||
tap: no
|
||||
ip:
|
||||
address:
|
||||
- 192.168.1.1/24
|
||||
```
|
||||
|
||||
Setting the IP configuration:
|
||||
|
||||
```yaml
|
||||
network_connections:
|
||||
- name: eth0
|
||||
type: ethernet
|
||||
ip:
|
||||
route_metric4: 100
|
||||
dhcp4: no
|
||||
#dhcp4_send_hostname: no
|
||||
gateway4: 192.0.2.1
|
||||
|
||||
dns:
|
||||
- 192.0.2.2
|
||||
- 198.51.100.5
|
||||
dns_search:
|
||||
- example.com
|
||||
- subdomain.example.com
|
||||
|
||||
route_metric6: -1
|
||||
auto6: no
|
||||
gateway6: 2001:db8::1
|
||||
|
||||
address:
|
||||
- 192.0.2.3/24
|
||||
- 198.51.100.3/26
|
||||
- 2001:db8::80/7
|
||||
|
||||
route:
|
||||
- network: 198.51.100.128
|
||||
prefix: 26
|
||||
gateway: 198.51.100.1
|
||||
metric: 2
|
||||
- network: 198.51.100.64
|
||||
prefix: 26
|
||||
gateway: 198.51.100.6
|
||||
metric: 4
|
||||
route_append_only: no
|
||||
rule_append_only: yes
|
||||
```
|
||||
|
||||
### Invalid and Wrong Configuration
|
||||
|
||||
The `network` role rejects invalid configurations. It is recommended to test the role
|
||||
with `--check` first. There is no protection against wrong (but valid) configuration.
|
||||
Double-check your configuration before applying it.
|
||||
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
The `network` role supports the same configuration scheme for both providers (`nm`
|
||||
and `initscripts`). That means, you can use the same playbook with NetworkManager
|
||||
and initscripts. However, note that not every option is handled exactly the same
|
||||
by every provider. Do a test run first with `--check`.
|
||||
|
||||
It is not supported to create a configuration for one provider, and expect another
|
||||
provider to handle them. For example, creating profiles with the `initscripts` provider,
|
||||
and later enabling NetworkManager is not guaranteed to work automatically. Possibly,
|
||||
you have to adjust the configuration so that it can be used by another provider.
|
||||
|
||||
For example, configuring a RHEL6 host with initscripts and upgrading to
|
||||
RHEL7 while continuing to use initscripts in RHEL7 is an acceptable scenario. What
|
||||
is not guaranteed is to upgrade to RHEL7, disable initscripts and expect NetworkManager
|
||||
to take over the configuration automatically.
|
||||
|
||||
Depending on NetworkManager's configuration, connections may be stored as ifcfg files
|
||||
as well, but it is not guaranteed that plain initscripts can handle these ifcfg files
|
||||
after disabling the NetworkManager service.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
As Ansible usually works via the network, for example via SSH, there are some limitations to be considered:
|
||||
|
||||
The `network` role does not support bootstraping networking configuration. One
|
||||
option may be [ansible-pull](https://docs.ansible.com/ansible/playbooks_intro.html#ansible-pull).
|
||||
Another option maybe be to initially auto-configure the host during installation
|
||||
(ISO based, kickstart, etc.), so that the host is connected to a management LAN
|
||||
or VLAN. It strongly depends on your environment.
|
||||
|
||||
For `initscripts` provider, deploying a profile merely means to create the ifcfg
|
||||
files. Nothing happens automatically until the play issues `ifup` or `ifdown`
|
||||
via the `up` or `down` [states](#state) -- unless there are other
|
||||
components that rely on the ifcfg files and react on changes.
|
||||
|
||||
The `initscripts` provider requires the different profiles to be in the right
|
||||
order when they depend on each other. For example the bonding master device
|
||||
needs to be specified before the slave devices.
|
||||
|
||||
When removing a profile for NetworkManager it also takes the connection
|
||||
down and possibly removes virtual interfaces. With the `initscripts` provider
|
||||
removing a profile does not change its current runtime state (this is a future
|
||||
feature for NetworkManager as well).
|
||||
|
||||
For NetworkManager, modifying a connection with autoconnect enabled
|
||||
may result in the activation of a new profile on a previously disconnected
|
||||
interface. Also, deleting a NetworkManager connection that is currently active
|
||||
results in removing the interface. Therefore, the order of the steps should be
|
||||
followed, and carefully handling of [autoconnect](#autoconnect) property may be
|
||||
necessary. This should be improved in NetworkManager RFE [rh#1401515](https://bugzilla.redhat.com/show_bug.cgi?id=1401515).
|
||||
|
||||
It seems difficult to change networking of the target host in a way that breaks
|
||||
the current SSH connection of ansible. If you want to do that, ansible-pull might
|
||||
be a solution. Alternatively, a combination of `async`/`poll` with changing
|
||||
the `ansible_host` midway of the play.
|
||||
|
||||
**TODO** The current role does not yet support to easily split the
|
||||
play in a pre-configure step, and a second step to activate the new configuration.
|
||||
|
||||
In general, to successfully run the play, determine which configuration is
|
||||
active in the first place, and then carefully configure a sequence of steps to change to
|
||||
the new configuration. The actual solution depends strongly on your environment.
|
||||
|
||||
### Handling potential problems
|
||||
|
||||
When something goes wrong while configuring networking remotely, you might need
|
||||
to get physical access to the machine to recover.
|
||||
|
||||
**TODO** NetworkManager supports a
|
||||
[checkpoint/rollback](https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html#gdbus-method-org-freedesktop-NetworkManager.CheckpointCreate)
|
||||
feature. At the beginning of the play we could create a checkpoint and if we lose
|
||||
connectivity due to an error, NetworkManager would automatically rollback after
|
||||
timeout. The limitations is that this would only work with NetworkManager, and
|
||||
it is not clear that rollback will result in a working configuration.
|
||||
82
roles/linux-system-roles.network/defaults/main.yml
Normal file
82
roles/linux-system-roles.network/defaults/main.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
network_connections: []
|
||||
|
||||
# Use initscripts for RHEL/CentOS < 7, nm otherwise
|
||||
network_provider_os_default: "{{
|
||||
'initscripts' if ansible_distribution in ['RedHat', 'CentOS'] and
|
||||
ansible_distribution_major_version is version('7', '<')
|
||||
else 'nm' }}"
|
||||
# If NetworkManager.service is running, assume that 'nm' is currently in-use,
|
||||
# otherwise initscripts
|
||||
network_provider_current: "{{
|
||||
'nm' if 'NetworkManager.service' in ansible_facts.services and
|
||||
ansible_facts.services['NetworkManager.service']['state'] == 'running'
|
||||
else 'initscripts'
|
||||
}}"
|
||||
# Default to the auto-detected value
|
||||
network_provider: "{{ network_provider_current }}"
|
||||
|
||||
# The python-gobject-base package depends on the python version and
|
||||
# distribution:
|
||||
# - python-gobject-base on RHEL7 (no python2-gobject-base :-/)
|
||||
# - python-gobject-base or python2-gobject-base on Fedora 27
|
||||
# - python3-gobject-base on Fedora 28+
|
||||
network_service_name_default_nm: NetworkManager
|
||||
network_packages_default_nm:
|
||||
- ethtool
|
||||
- NetworkManager
|
||||
- "python{{ ansible_python['version']['major'] | replace('2', '') }}-gobject-base"
|
||||
|
||||
network_service_name_default_initscripts: network
|
||||
|
||||
# initscripts requires bridge-utils to manage bridges, install it when the
|
||||
# 'bridge' type is used in network_connections
|
||||
_network_packages_default_initscripts_bridge: ["{% if ['bridge'] in network_connections|json_query('[*][type]') and
|
||||
(
|
||||
(ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('7', '<=')) or
|
||||
(ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('28', '<='))
|
||||
)
|
||||
%}bridge-utils{% endif %}"]
|
||||
_network_packages_default_initscripts_network_scripts: ["{%
|
||||
if (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('7', '<=')) or
|
||||
(ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('28', '<='))
|
||||
%}initscripts{% else %}network-scripts{% endif %}"]
|
||||
# convert _network_packages_default_initscripts_bridge to an empty list if it
|
||||
# contains only the empty string and add it to the default package list
|
||||
# |select() filters the list to include only values that evaluate to true
|
||||
# (the empty string is false)
|
||||
# |list() converts the generator that |select() creates to a list
|
||||
network_packages_default_initscripts: "{{ ['ethtool']
|
||||
+ _network_packages_default_initscripts_bridge|select()|list()
|
||||
+ _network_packages_default_initscripts_network_scripts|select()|list()
|
||||
}}"
|
||||
|
||||
|
||||
# The user can explicitly set host variables "network_provider",
|
||||
# "network_service_name" and "network_packages".
|
||||
#
|
||||
# Usually, the user only wants to select the "network_provider"
|
||||
# (or not set it at all and let it be autodetected via the
|
||||
# internal variable "{{ network_provider_current }}". Hence,
|
||||
# depending on the "network_provider", a different set of
|
||||
# service-name and packages is chosen.
|
||||
#
|
||||
# That is done via the internal "_network_provider_setup" dictionary.
|
||||
# If the user doesn't explicitly set "network_service_name" or
|
||||
# "network_packages" (which he usually wouldn't), then the defaults
|
||||
# from "network_service_name_default_*" and "network_packages_default_*"
|
||||
# apply. These values are hard-coded in this file, but they also could
|
||||
# be overwritten as host variables or via vars/*.yml.
|
||||
_network_provider_setup:
|
||||
nm:
|
||||
service_name: "{{ network_service_name_default_nm }}"
|
||||
packages: "{{ network_packages_default_nm }}"
|
||||
initscripts:
|
||||
service_name: "{{ network_service_name_default_initscripts }}"
|
||||
packages: "{{ network_packages_default_initscripts }}"
|
||||
|
||||
network_packages: "{{
|
||||
_network_provider_setup[network_provider]['packages'] }}"
|
||||
network_service_name: "{{
|
||||
_network_provider_setup[network_provider]['service_name'] }}"
|
||||
38
roles/linux-system-roles.network/examples/bond-with-vlan.yml
Normal file
38
roles/linux-system-roles.network/examples/bond-with-vlan.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
# Create a bond profile, which is the parent of VLAN.
|
||||
- name: prod2
|
||||
state: up
|
||||
type: bond
|
||||
interface_name: bond2
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
bond:
|
||||
mode: active-backup
|
||||
miimon: 110
|
||||
|
||||
# enslave an ethernet to the bond
|
||||
- name: prod2-slave1
|
||||
state: up
|
||||
type: ethernet
|
||||
interface_name: "{{ network_interface_name2 }}"
|
||||
master: prod2
|
||||
|
||||
# on top of it, create a VLAN with ID 100 and static
|
||||
# addressing
|
||||
- name: prod2.100
|
||||
state: up
|
||||
type: vlan
|
||||
parent: prod2
|
||||
vlan_id: 100
|
||||
ip:
|
||||
address:
|
||||
- "192.0.2.{{ network_iphost }}/24"
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
@@ -0,0 +1,36 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
# Create a bridge profile, which is the parent of VLAN.
|
||||
- name: prod2
|
||||
state: up
|
||||
type: bridge
|
||||
interface_name: bridge2
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
|
||||
# enslave an ethernet to the bridge
|
||||
- name: prod2-slave1
|
||||
state: up
|
||||
type: ethernet
|
||||
interface_name: "{{ network_interface_name2 }}"
|
||||
master: prod2
|
||||
slave_type: bridge
|
||||
|
||||
# on top of it, create a VLAN with ID 100 and static
|
||||
# addressing
|
||||
- name: prod2.100
|
||||
state: up
|
||||
type: vlan
|
||||
parent: prod2
|
||||
vlan_id: 100
|
||||
ip:
|
||||
address:
|
||||
- "192.0.2.{{ network_iphost }}/24"
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
1
roles/linux-system-roles.network/examples/down-profile.yml
Symbolic link
1
roles/linux-system-roles.network/examples/down-profile.yml
Symbolic link
@@ -0,0 +1 @@
|
||||
../tests/down-profile.yml
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
# Create one ethernet profile and activate it.
|
||||
# The profile uses automatic IP addressing
|
||||
# and is tied to the interface by MAC address.
|
||||
- name: prod1
|
||||
state: up
|
||||
type: ethernet
|
||||
autoconnect: yes
|
||||
mac: "{{ network_mac1 }}"
|
||||
mtu: 1450
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
29
roles/linux-system-roles.network/examples/eth-with-vlan.yml
Normal file
29
roles/linux-system-roles.network/examples/eth-with-vlan.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
# Create a profile for the underlying device of the VLAN.
|
||||
- name: prod2
|
||||
type: ethernet
|
||||
autoconnect: no
|
||||
state: up
|
||||
interface_name: "{{ network_interface_name2 }}"
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
|
||||
# on top of it, create a VLAN with ID 100 and static
|
||||
# addressing
|
||||
- name: prod2.100
|
||||
state: up
|
||||
type: vlan
|
||||
parent: prod2
|
||||
vlan_id: 100
|
||||
ip:
|
||||
address:
|
||||
- "192.0.2.{{ network_iphost }}/24"
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
@@ -0,0 +1,14 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- include_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ network_interface_name1 }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
ip:
|
||||
dhcp4: "no"
|
||||
auto6: "no"
|
||||
@@ -0,0 +1,19 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- include_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ network_interface_name1 }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
ip:
|
||||
dhcp4: "no"
|
||||
auto6: "no"
|
||||
ethtool:
|
||||
features:
|
||||
gro: "no"
|
||||
gso: "yes"
|
||||
tx-sctp-segmentation: "no"
|
||||
26
roles/linux-system-roles.network/examples/infiniband.yml
Normal file
26
roles/linux-system-roles.network/examples/infiniband.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
- name: ib0
|
||||
type: infiniband
|
||||
interface_name: ib0
|
||||
|
||||
# Create a simple infiniband profile
|
||||
- name: ib0-10
|
||||
interface_name: ib0.000a
|
||||
type: infiniband
|
||||
autoconnect: yes
|
||||
infiniband_p_key: 10
|
||||
parent: ib0
|
||||
state: up
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
address:
|
||||
- 198.51.100.133/30
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
5
roles/linux-system-roles.network/examples/inventory
Normal file
5
roles/linux-system-roles.network/examples/inventory
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# an inventory for the examples.
|
||||
[network-test]
|
||||
v-rhel6 ansible_user=root network_iphost=196 network_mac1=00:00:5e:00:53:00 network_interface_name1=eth0 network_interface_name2=eth1
|
||||
v-rhel7 ansible_user=root network_iphost=97 network_mac1=00:00:5e:00:53:01 network_interface_name1=eth0 network_interface_name2=eth1
|
||||
29
roles/linux-system-roles.network/examples/macvlan.yml
Normal file
29
roles/linux-system-roles.network/examples/macvlan.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
- name: eth0
|
||||
type: ethernet
|
||||
state: up
|
||||
interface_name: eth0
|
||||
ip:
|
||||
address:
|
||||
- 192.168.0.1/24
|
||||
|
||||
# Create a virtual ethernet card bound to eth0
|
||||
- name: veth0
|
||||
type: macvlan
|
||||
state: up
|
||||
parent: eth0
|
||||
macvlan:
|
||||
mode: bridge
|
||||
promiscuous: True
|
||||
tap: False
|
||||
ip:
|
||||
address:
|
||||
- 192.168.1.1/24
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
1
roles/linux-system-roles.network/examples/remove-profile.yml
Symbolic link
1
roles/linux-system-roles.network/examples/remove-profile.yml
Symbolic link
@@ -0,0 +1 @@
|
||||
../tests/remove-profile.yml
|
||||
1
roles/linux-system-roles.network/examples/roles
Symbolic link
1
roles/linux-system-roles.network/examples/roles
Symbolic link
@@ -0,0 +1 @@
|
||||
../tests/roles/
|
||||
2380
roles/linux-system-roles.network/library/network_connections.py
Normal file
2380
roles/linux-system-roles.network/library/network_connections.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
install_date: Wed Jul 1 18:41:54 2020
|
||||
version: 1.1.0
|
||||
27
roles/linux-system-roles.network/meta/main.yml
Normal file
27
roles/linux-system-roles.network/meta/main.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
galaxy_info:
|
||||
author: Thomas Haller <thaller@redhat.com>, Till Maas <till@redhat.com>
|
||||
description: Configure networking
|
||||
company: Red Hat, Inc.
|
||||
license: BSD-3-Clause
|
||||
min_ansible_version: 2.5
|
||||
galaxy_tags:
|
||||
- centos
|
||||
- fedora
|
||||
- network
|
||||
- networking
|
||||
- redhat
|
||||
- rhel
|
||||
- system
|
||||
platforms:
|
||||
- name: Fedora
|
||||
versions:
|
||||
- 28
|
||||
- 29
|
||||
- 30
|
||||
- name: EL
|
||||
versions:
|
||||
- 6
|
||||
- 7
|
||||
- 8
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/python3 -tt
|
||||
# vim: fileencoding=utf8
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
|
||||
class MyError(Exception):
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
""" Support for NetworkManager aka the NM provider """
|
||||
|
||||
# pylint: disable=import-error, no-name-in-module
|
||||
from ansible.module_utils.network_lsr.utils import Util
|
||||
|
||||
ETHTOOL_FEATURE_PREFIX = "ETHTOOL_OPTNAME_FEATURE_"
|
||||
|
||||
|
||||
def get_nm_ethtool_feature(name):
|
||||
"""
|
||||
Translate ethtool feature into Network Manager name
|
||||
|
||||
:param name: Name of the feature
|
||||
:type name: str
|
||||
:returns: Name of the feature to be used by `NM.SettingEthtool.set_feature()`
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
name = ETHTOOL_FEATURE_PREFIX + name.upper().replace("-", "_")
|
||||
|
||||
feature = getattr(Util.NM(), name, None)
|
||||
return feature
|
||||
@@ -0,0 +1,338 @@
|
||||
#!/usr/bin/python3 -tt
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# vim: fileencoding=utf8
|
||||
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
# pylint: disable=import-error, no-name-in-module
|
||||
from ansible.module_utils.network_lsr import MyError
|
||||
|
||||
|
||||
class Util:
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
STRING_TYPE = str if PY3 else basestring # noqa:F821
|
||||
|
||||
@staticmethod
|
||||
def first(iterable, default=None, pred=None):
|
||||
for v in iterable:
|
||||
if pred is None or pred(v):
|
||||
return v
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def check_output(argv):
|
||||
# subprocess.check_output is python 2.7.
|
||||
with open("/dev/null", "wb") as DEVNULL:
|
||||
import subprocess
|
||||
|
||||
env = os.environ.copy()
|
||||
env["LANG"] = "C"
|
||||
p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=DEVNULL, env=env)
|
||||
# FIXME: Can we assume this to always be UTF-8?
|
||||
out = p.communicate()[0].decode("UTF-8")
|
||||
if p.returncode != 0:
|
||||
raise MyError("failure calling %s: exit with %s" % (argv, p.returncode))
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
def create_uuid(cls):
|
||||
return str(uuid.uuid4())
|
||||
|
||||
@classmethod
|
||||
def NM(cls):
|
||||
n = getattr(cls, "_NM", None)
|
||||
if n is None:
|
||||
# Installing pygobject in a tox virtualenv does not work out of the
|
||||
# box
|
||||
# pylint: disable=import-error
|
||||
import gi
|
||||
|
||||
gi.require_version("NM", "1.0")
|
||||
from gi.repository import NM, GLib, Gio, GObject
|
||||
|
||||
cls._NM = NM
|
||||
cls._GLib = GLib
|
||||
cls._Gio = Gio
|
||||
cls._GObject = GObject
|
||||
n = NM
|
||||
return n
|
||||
|
||||
@classmethod
|
||||
def GLib(cls):
|
||||
cls.NM()
|
||||
return cls._GLib
|
||||
|
||||
@classmethod
|
||||
def Gio(cls):
|
||||
cls.NM()
|
||||
return cls._Gio
|
||||
|
||||
@classmethod
|
||||
def GObject(cls):
|
||||
cls.NM()
|
||||
return cls._GObject
|
||||
|
||||
@classmethod
|
||||
def Timestamp(cls):
|
||||
return cls.GLib().get_monotonic_time()
|
||||
|
||||
@classmethod
|
||||
def GMainLoop(cls):
|
||||
gmainloop = getattr(cls, "_GMainLoop", None)
|
||||
if gmainloop is None:
|
||||
gmainloop = cls.GLib().MainLoop()
|
||||
cls._GMainLoop = gmainloop
|
||||
return gmainloop
|
||||
|
||||
@classmethod
|
||||
def GMainLoop_run(cls, timeout=None):
|
||||
if timeout is None:
|
||||
cls.GMainLoop().run()
|
||||
return True
|
||||
|
||||
GLib = cls.GLib()
|
||||
timeout_reached = []
|
||||
loop = cls.GMainLoop()
|
||||
|
||||
def _timeout_cb(unused):
|
||||
timeout_reached.append(1)
|
||||
loop.quit()
|
||||
return False
|
||||
|
||||
timeout_id = GLib.timeout_add(int(timeout * 1000), _timeout_cb, None)
|
||||
loop.run()
|
||||
if not timeout_reached:
|
||||
GLib.source_remove(timeout_id)
|
||||
return not timeout_reached
|
||||
|
||||
@classmethod
|
||||
def GMainLoop_iterate(cls, may_block=False):
|
||||
return cls.GMainLoop().get_context().iteration(may_block)
|
||||
|
||||
@classmethod
|
||||
def GMainLoop_iterate_all(cls):
|
||||
c = 0
|
||||
while cls.GMainLoop_iterate():
|
||||
c += 1
|
||||
return c
|
||||
|
||||
@classmethod
|
||||
def call_async_method(cls, object_, action, args, mainloop_timeout=10):
|
||||
""" Asynchronously call a NetworkManager method """
|
||||
cancellable = cls.create_cancellable()
|
||||
async_action = action + "_async"
|
||||
# NM does not use a uniform naming for the async methods,
|
||||
# for checkpoints it is:
|
||||
# NMClient.checkpoint_create() and NMClient.checkpoint_create_finish(),
|
||||
# but for reapply it is:
|
||||
# NMDevice.reapply_async() and NMDevice.reapply_finish()
|
||||
# NMDevice.reapply() is a synchronous version
|
||||
# Therefore check if there is a method if an `async` suffix and use the
|
||||
# one without the suffix otherwise
|
||||
if not hasattr(object_, async_action):
|
||||
async_action = action
|
||||
finish = action + "_finish"
|
||||
user_data = {}
|
||||
|
||||
fullargs = []
|
||||
fullargs += args
|
||||
fullargs += (cancellable, cls.create_callback(finish), user_data)
|
||||
|
||||
getattr(object_, async_action)(*fullargs)
|
||||
|
||||
if not cls.GMainLoop_run(mainloop_timeout):
|
||||
cancellable.cancel()
|
||||
raise MyError("failure to call %s.%s(): timeout" % object_, async_action)
|
||||
|
||||
success = user_data.get("success", None)
|
||||
if success is not None:
|
||||
return success
|
||||
|
||||
raise MyError(
|
||||
"failure to %s checkpoint: %s: %r"
|
||||
% (action, user_data.get("error", "unknown error"), user_data)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create_cancellable(cls):
|
||||
return cls.Gio().Cancellable.new()
|
||||
|
||||
@classmethod
|
||||
def create_callback(cls, finish_method):
|
||||
"""
|
||||
Create a callback that will return the result of the finish method and
|
||||
quit the GMainLoop
|
||||
|
||||
:param finish_method str: Name of the finish method to call from the
|
||||
source object in the callback
|
||||
"""
|
||||
|
||||
def callback(source_object, res, user_data):
|
||||
success = None
|
||||
try:
|
||||
success = getattr(source_object, finish_method)(res)
|
||||
except Exception as e:
|
||||
if cls.error_is_cancelled(e):
|
||||
return
|
||||
user_data["error"] = str(e)
|
||||
user_data["success"] = success
|
||||
cls.GMainLoop().quit()
|
||||
|
||||
return callback
|
||||
|
||||
@classmethod
|
||||
def error_is_cancelled(cls, e):
|
||||
GLib = cls.GLib()
|
||||
if isinstance(e, GLib.GError):
|
||||
if (
|
||||
e.domain == "g-io-error-quark"
|
||||
and e.code == cls.Gio().IOErrorEnum.CANCELLED
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def ifname_valid(ifname):
|
||||
# see dev_valid_name() in kernel's net/core/dev.c
|
||||
if not ifname:
|
||||
return False
|
||||
if ifname in [".", ".."]:
|
||||
return False
|
||||
if len(ifname) >= 16:
|
||||
return False
|
||||
if any([c == "/" or c == ":" or c.isspace() for c in ifname]):
|
||||
return False
|
||||
# FIXME: encoding issues regarding python unicode string
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def mac_aton(mac_str, force_len=None):
|
||||
# we also accept None and '' for convenience.
|
||||
# - None yiels None
|
||||
# - '' yields []
|
||||
if mac_str is None:
|
||||
return mac_str
|
||||
i = 0
|
||||
b = []
|
||||
for c in mac_str:
|
||||
if i == 2:
|
||||
if c != ":":
|
||||
raise MyError("not a valid MAC address: '%s'" % (mac_str))
|
||||
i = 0
|
||||
continue
|
||||
try:
|
||||
if i == 0:
|
||||
n = int(c, 16) * 16
|
||||
i = 1
|
||||
else:
|
||||
assert i == 1
|
||||
n = n + int(c, 16)
|
||||
i = 2
|
||||
b.append(n)
|
||||
except Exception:
|
||||
raise MyError("not a valid MAC address: '%s'" % (mac_str))
|
||||
if i == 1:
|
||||
raise MyError("not a valid MAC address: '%s'" % (mac_str))
|
||||
if force_len is not None:
|
||||
if force_len != len(b):
|
||||
raise MyError(
|
||||
"not a valid MAC address of length %s: '%s'" % (force_len, mac_str)
|
||||
)
|
||||
return b
|
||||
|
||||
@staticmethod
|
||||
def mac_ntoa(mac):
|
||||
if mac is None:
|
||||
return None
|
||||
return ":".join(["%02x" % c for c in mac])
|
||||
|
||||
@staticmethod
|
||||
def mac_norm(mac_str, force_len=None):
|
||||
return Util.mac_ntoa(Util.mac_aton(mac_str, force_len))
|
||||
|
||||
@staticmethod
|
||||
def boolean(arg):
|
||||
if arg is None or isinstance(arg, bool):
|
||||
return arg
|
||||
arg0 = arg
|
||||
if isinstance(arg, Util.STRING_TYPE):
|
||||
arg = arg.lower()
|
||||
|
||||
if arg in ["y", "yes", "on", "1", "true", 1, True]:
|
||||
return True
|
||||
if arg in ["n", "no", "off", "0", "false", 0, False]:
|
||||
return False
|
||||
|
||||
raise MyError("value '%s' is not a boolean" % (arg0))
|
||||
|
||||
@staticmethod
|
||||
def parse_ip(addr, family=None):
|
||||
if addr is None:
|
||||
return (None, None)
|
||||
if family is not None:
|
||||
Util.addr_family_check(family)
|
||||
a = socket.inet_pton(family, addr)
|
||||
else:
|
||||
a = None
|
||||
family = None
|
||||
try:
|
||||
a = socket.inet_pton(socket.AF_INET, addr)
|
||||
family = socket.AF_INET
|
||||
except Exception:
|
||||
a = socket.inet_pton(socket.AF_INET6, addr)
|
||||
family = socket.AF_INET6
|
||||
return (socket.inet_ntop(family, a), family)
|
||||
|
||||
@staticmethod
|
||||
def addr_family_check(family):
|
||||
if family != socket.AF_INET and family != socket.AF_INET6:
|
||||
raise MyError("invalid address family %s" % (family))
|
||||
|
||||
@staticmethod
|
||||
def addr_family_to_v(family):
|
||||
if family is None:
|
||||
return ""
|
||||
if family == socket.AF_INET:
|
||||
return "v4"
|
||||
if family == socket.AF_INET6:
|
||||
return "v6"
|
||||
raise MyError("invalid address family '%s'" % (family))
|
||||
|
||||
@staticmethod
|
||||
def addr_family_default_prefix(family):
|
||||
Util.addr_family_check(family)
|
||||
if family == socket.AF_INET:
|
||||
return 24
|
||||
else:
|
||||
return 64
|
||||
|
||||
@staticmethod
|
||||
def addr_family_valid_prefix(family, prefix):
|
||||
Util.addr_family_check(family)
|
||||
if family == socket.AF_INET:
|
||||
m = 32
|
||||
else:
|
||||
m = 128
|
||||
return prefix >= 0 and prefix <= m
|
||||
|
||||
@staticmethod
|
||||
def parse_address(address, family=None):
|
||||
try:
|
||||
parts = address.split()
|
||||
addr_parts = parts[0].split("/")
|
||||
if len(addr_parts) != 2:
|
||||
raise MyError("expect two addr-parts: ADDR/PLEN")
|
||||
a, family = Util.parse_ip(addr_parts[0], family)
|
||||
prefix = int(addr_parts[1])
|
||||
if not Util.addr_family_valid_prefix(family, prefix):
|
||||
raise MyError("invalid prefix %s" % (prefix))
|
||||
if len(parts) > 1:
|
||||
raise MyError("too many parts")
|
||||
return {"address": a, "family": family, "prefix": prefix}
|
||||
except Exception:
|
||||
raise MyError("invalid address '%s'" % (address))
|
||||
@@ -0,0 +1,14 @@
|
||||
# Molecule managed
|
||||
|
||||
{% if item.registry is defined %}
|
||||
FROM {{ item.registry.url }}/{{ item.image }}
|
||||
{% else %}
|
||||
FROM {{ item.image }}
|
||||
{% endif %}
|
||||
|
||||
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
|
||||
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python2-dnf bash && dnf clean all; \
|
||||
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
|
||||
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
|
||||
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
|
||||
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
dependency:
|
||||
name: galaxy
|
||||
driver:
|
||||
name: docker
|
||||
lint:
|
||||
name: yamllint
|
||||
options:
|
||||
config-file: molecule/default/yamllint.yml
|
||||
platforms:
|
||||
- name: centos-6
|
||||
image: linuxsystemroles/centos-6
|
||||
privileged: true
|
||||
- name: centos-7
|
||||
image: linuxsystemroles/centos-7
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup:ro
|
||||
privileged: true
|
||||
provisioner:
|
||||
name: ansible
|
||||
log: true
|
||||
lint:
|
||||
name: ansible-lint
|
||||
playbooks:
|
||||
converge: ../../tests/tests_default.yml
|
||||
scenario:
|
||||
name: default
|
||||
test_sequence:
|
||||
- destroy
|
||||
- create
|
||||
- converge
|
||||
- idempotence
|
||||
- check
|
||||
- destroy
|
||||
verifier:
|
||||
name: testinfra
|
||||
lint:
|
||||
name: flake8
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
extends: default
|
||||
rules:
|
||||
braces:
|
||||
max-spaces-inside: 1
|
||||
level: error
|
||||
brackets:
|
||||
max-spaces-inside: 1
|
||||
level: error
|
||||
line-length: disable
|
||||
truthy: disable
|
||||
document-start: disable
|
||||
545
roles/linux-system-roles.network/pylintrc
Normal file
545
roles/linux-system-roles.network/pylintrc
Normal file
@@ -0,0 +1,545 @@
|
||||
# This file was generated using `pylint --generate-rcfile > pylintrc` command.
|
||||
[MASTER]
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code
|
||||
extension-pkg-whitelist=
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not
|
||||
# paths.
|
||||
ignore=CVS
|
||||
|
||||
# Add files or directories matching the regex patterns to the blacklist. The
|
||||
# regex matches against base names, not paths.
|
||||
ignore-patterns=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc()) + '/library'); sys.path.append(os.path.dirname(find_pylintrc()) + '/module_utils'); sys.path.append(os.path.dirname(find_pylintrc()) + '/tests')"
|
||||
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
jobs=1
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
||||
# user-friendly hints instead of false-positive error messages
|
||||
suggestion-mode=yes
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=
|
||||
#disable=print-statement,
|
||||
# parameter-unpacking,
|
||||
# unpacking-in-except,
|
||||
# old-raise-syntax,
|
||||
# backtick,
|
||||
# long-suffix,
|
||||
# old-ne-operator,
|
||||
# old-octal-literal,
|
||||
# import-star-module-level,
|
||||
# non-ascii-bytes-literal,
|
||||
# raw-checker-failed,
|
||||
# bad-inline-option,
|
||||
# locally-disabled,
|
||||
# locally-enabled,
|
||||
# file-ignored,
|
||||
# suppressed-message,
|
||||
# useless-suppression,
|
||||
# deprecated-pragma,
|
||||
# apply-builtin,
|
||||
# basestring-builtin,
|
||||
# buffer-builtin,
|
||||
# cmp-builtin,
|
||||
# coerce-builtin,
|
||||
# execfile-builtin,
|
||||
# file-builtin,
|
||||
# long-builtin,
|
||||
# raw_input-builtin,
|
||||
# reduce-builtin,
|
||||
# standarderror-builtin,
|
||||
# unicode-builtin,
|
||||
# xrange-builtin,
|
||||
# coerce-method,
|
||||
# delslice-method,
|
||||
# getslice-method,
|
||||
# setslice-method,
|
||||
# no-absolute-import,
|
||||
# old-division,
|
||||
# dict-iter-method,
|
||||
# dict-view-method,
|
||||
# next-method-called,
|
||||
# metaclass-assignment,
|
||||
# indexing-exception,
|
||||
# raising-string,
|
||||
# reload-builtin,
|
||||
# oct-method,
|
||||
# hex-method,
|
||||
# nonzero-method,
|
||||
# cmp-method,
|
||||
# input-builtin,
|
||||
# round-builtin,
|
||||
# intern-builtin,
|
||||
# unichr-builtin,
|
||||
# map-builtin-not-iterating,
|
||||
# zip-builtin-not-iterating,
|
||||
# range-builtin-not-iterating,
|
||||
# filter-builtin-not-iterating,
|
||||
# using-cmp-argument,
|
||||
# eq-without-hash,
|
||||
# div-method,
|
||||
# idiv-method,
|
||||
# rdiv-method,
|
||||
# exception-message-attribute,
|
||||
# invalid-str-codec,
|
||||
# sys-max-int,
|
||||
# bad-python3-import,
|
||||
# deprecated-string-function,
|
||||
# deprecated-str-translate-call,
|
||||
# deprecated-itertools-function,
|
||||
# deprecated-types-field,
|
||||
# next-method-defined,
|
||||
# dict-items-not-iterating,
|
||||
# dict-keys-not-iterating,
|
||||
# dict-values-not-iterating
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
enable=c-extension-no-member
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, json
|
||||
# and msvs (visual studio).You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Activate the evaluation score.
|
||||
score=yes
|
||||
|
||||
|
||||
[REFACTORING]
|
||||
|
||||
# Maximum number of nested blocks for function / method body
|
||||
max-nested-blocks=5
|
||||
|
||||
# Complete name of functions that never returns. When checking for
|
||||
# inconsistent-return-statements if a never returning function is called then
|
||||
# it will be considered as an explicit return statement and no message will be
|
||||
# printed.
|
||||
never-returning-functions=optparse.Values,sys.exit
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# List of decorators that produce context managers, such as
|
||||
# contextlib.contextmanager. Add to this list to register other decorators that
|
||||
# produce valid context managers.
|
||||
contextmanager-decorators=contextlib.contextmanager
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# This flag controls whether pylint should warn about no-member and similar
|
||||
# checks whenever an opaque object is returned when inferring. The inference
|
||||
# can return multiple potential results while evaluating a Python object, but
|
||||
# some branches might not be evaluated, which results in partial inference. In
|
||||
# that case, it might be useful to still emit no-member and other checks for
|
||||
# the rest of the inferred objects.
|
||||
ignore-on-opaque-inference=yes
|
||||
|
||||
# List of class names for which member attributes should not be checked (useful
|
||||
# for classes with dynamically set attributes). This supports the use of
|
||||
# qualified names.
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis. It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=
|
||||
|
||||
# Show a hint with possible names when a member name was not found. The aspect
|
||||
# of finding the hint is based on edit distance.
|
||||
missing-member-hint=yes
|
||||
|
||||
# The minimum edit distance a name should have in order to be considered a
|
||||
# similar match for a missing member name.
|
||||
missing-member-hint-distance=1
|
||||
|
||||
# The total number of similar names that should be taken in consideration when
|
||||
# showing a hint for a missing member.
|
||||
missing-member-max-choices=1
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=100
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# List of optional constructs for which whitespace checking is disabled. `dict-
|
||||
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
|
||||
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
|
||||
# `empty-line` allows space-only lines.
|
||||
no-space-check=trailing-comma,
|
||||
dict-separator
|
||||
|
||||
# Allow the body of a class to be on the same line as the declaration if body
|
||||
# contains single statement.
|
||||
single-line-class-stmt=no
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Limits count of emitted suggestions for spelling mistakes
|
||||
max-spelling-suggestions=4
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||
# install python-enchant package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in
|
||||
# --spelling-private-dict-file option instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# Tells whether unused global variables should be treated as a violation.
|
||||
allow-global-unused-variables=yes
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,
|
||||
_cb
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore
|
||||
ignored-argument-names=_.*|^ignored_|^unused_
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# List of qualified module names which can have objects that can redefine
|
||||
# builtins.
|
||||
redefining-builtins-modules=six.moves,past.builtins,future.builtins
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Naming style matching correct argument names
|
||||
argument-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct argument names. Overrides argument-
|
||||
# naming-style
|
||||
#argument-rgx=
|
||||
|
||||
# Naming style matching correct attribute names
|
||||
attr-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct attribute names. Overrides attr-naming-
|
||||
# style
|
||||
#attr-rgx=
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,
|
||||
bar,
|
||||
baz,
|
||||
toto,
|
||||
tutu,
|
||||
tata
|
||||
|
||||
# Naming style matching correct class attribute names
|
||||
class-attribute-naming-style=any
|
||||
|
||||
# Regular expression matching correct class attribute names. Overrides class-
|
||||
# attribute-naming-style
|
||||
#class-attribute-rgx=
|
||||
|
||||
# Naming style matching correct class names
|
||||
class-naming-style=PascalCase
|
||||
|
||||
# Regular expression matching correct class names. Overrides class-naming-style
|
||||
#class-rgx=
|
||||
|
||||
# Naming style matching correct constant names
|
||||
const-naming-style=UPPER_CASE
|
||||
|
||||
# Regular expression matching correct constant names. Overrides const-naming-
|
||||
# style
|
||||
#const-rgx=
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
# Naming style matching correct function names
|
||||
function-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct function names. Overrides function-
|
||||
# naming-style
|
||||
#function-rgx=
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,
|
||||
j,
|
||||
k,
|
||||
ex,
|
||||
Run,
|
||||
_
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# Naming style matching correct inline iteration names
|
||||
inlinevar-naming-style=any
|
||||
|
||||
# Regular expression matching correct inline iteration names. Overrides
|
||||
# inlinevar-naming-style
|
||||
#inlinevar-rgx=
|
||||
|
||||
# Naming style matching correct method names
|
||||
method-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct method names. Overrides method-naming-
|
||||
# style
|
||||
#method-rgx=
|
||||
|
||||
# Naming style matching correct module names
|
||||
module-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct module names. Overrides module-naming-
|
||||
# style
|
||||
#module-rgx=
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=^_
|
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||
# to this list to register other decorators that produce valid properties.
|
||||
property-classes=abc.abstractproperty
|
||||
|
||||
# Naming style matching correct variable names
|
||||
variable-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct variable names. Overrides variable-
|
||||
# naming-style
|
||||
#variable-rgx=
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,
|
||||
XXX,
|
||||
TODO
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Allow wildcard imports from modules that define __all__.
|
||||
allow-wildcard-with-all=no
|
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||
# 3 compatible code, which means that the block might have code that exists
|
||||
# only in one or another interpreter, leading to false positives when analysed.
|
||||
analyse-fallback-blocks=no
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,
|
||||
TERMIOS,
|
||||
Bastion,
|
||||
rexec
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
# Force import order to recognize a module as part of the standard
|
||||
# compatibility libraries.
|
||||
known-standard-library=
|
||||
|
||||
# Force import order to recognize a module as part of a third party library.
|
||||
known-third-party=enchant
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=5
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Maximum number of boolean expressions in a if statement
|
||||
max-bool-expr=5
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=Exception
|
||||
58
roles/linux-system-roles.network/tasks/main.yml
Normal file
58
roles/linux-system-roles.network/tasks/main.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# get service facts, used in defaults/main.yml
|
||||
---
|
||||
- name: Check which services are running
|
||||
service_facts:
|
||||
no_log: true
|
||||
|
||||
# needed for ansible_facts.packages
|
||||
- name: Check which packages are installed
|
||||
package_facts:
|
||||
no_log: true
|
||||
|
||||
- name: Print network provider
|
||||
debug:
|
||||
msg: "Using network provider: {{ network_provider }}"
|
||||
|
||||
# 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
|
||||
- name: Install packages
|
||||
package:
|
||||
name: "{{ network_packages }}"
|
||||
state: present
|
||||
when:
|
||||
- not network_packages is subset(ansible_facts.packages.keys())
|
||||
|
||||
- name: Enable and start NetworkManager
|
||||
service:
|
||||
name: "{{ network_service_name }}"
|
||||
state: started
|
||||
enabled: true
|
||||
when:
|
||||
- network_provider == "nm"
|
||||
|
||||
- name: Enable network service
|
||||
service:
|
||||
name: "{{ network_service_name }}"
|
||||
enabled: true
|
||||
when:
|
||||
- network_provider == "initscripts"
|
||||
|
||||
- name: Ensure initscripts network file dependency is present
|
||||
copy:
|
||||
dest: /etc/sysconfig/network
|
||||
content: "# Created by network system role"
|
||||
force: false
|
||||
when:
|
||||
- network_provider == "initscripts"
|
||||
|
||||
- name: Configure networking connection profiles
|
||||
network_connections:
|
||||
provider: "{{ network_provider | mandatory }}"
|
||||
ignore_errors: "{{ network_ignore_errors | default(omit) }}"
|
||||
force_state_change: "{{ network_force_state_change | default(omit) }}"
|
||||
connections: "{{ network_connections | default([]) }}"
|
||||
|
||||
- name: Re-test connectivity
|
||||
ping:
|
||||
2
roles/linux-system-roles.network/tests/.gitignore
vendored
Normal file
2
roles/linux-system-roles.network/tests/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/*.retry
|
||||
/inventory
|
||||
@@ -0,0 +1 @@
|
||||
roles/linux-system-roles.network/library/network_connections.py
|
||||
16
roles/linux-system-roles.network/tests/covstats
Executable file
16
roles/linux-system-roles.network/tests/covstats
Executable file
@@ -0,0 +1,16 @@
|
||||
#! /bin/bash
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if [ "$#" -lt 1 ]
|
||||
then
|
||||
echo "USAGE: ${0} coverage_data_file..."
|
||||
echo "Show Statistics for each coverage data file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for coverage_file in "${@}"
|
||||
do
|
||||
echo "coverage statistics for: ${coverage_file}:"
|
||||
COVERAGE_FILE="${coverage_file}" coverage report
|
||||
echo
|
||||
done
|
||||
10
roles/linux-system-roles.network/tests/down-profile.yml
Normal file
10
roles/linux-system-roles.network/tests/down-profile.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: Set {{ profile }} down
|
||||
hosts: all
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ profile }}"
|
||||
state: down
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
109
roles/linux-system-roles.network/tests/ensure_non_running_provider.py
Executable file
109
roles/linux-system-roles.network/tests/ensure_non_running_provider.py
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
""" Check that there is a playbook to run all role tests with the non-default
|
||||
provider as well """
|
||||
# vim: fileencoding=utf8
|
||||
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
import yaml
|
||||
|
||||
OTHER_PROVIDER_SUFFIX = "_other_provider.yml"
|
||||
|
||||
IGNORE = [
|
||||
"tests_helpers-and-asserts.yml",
|
||||
"tests_states.yml",
|
||||
"tests_unit.yml",
|
||||
"tests_vlan_mtu_initscripts.yml",
|
||||
"tests_vlan_mtu_nm.yml",
|
||||
"tests_ethtool_features_initscripts.yml",
|
||||
"tests_ethtool_features_nm.yml",
|
||||
]
|
||||
|
||||
OTHER_PLAYBOOK = """
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: Run playbook '{tests_playbook}' with non-default provider
|
||||
hosts: all
|
||||
vars:
|
||||
network_provider_current:
|
||||
tasks:
|
||||
# required for the code to set network_provider_current
|
||||
- name: Get service facts
|
||||
service_facts:
|
||||
- name: Set network provider
|
||||
set_fact:
|
||||
network_provider: '{{{{ "initscripts" if network_provider_current == "nm"
|
||||
else "nm" }}}}'
|
||||
|
||||
- import_playbook: "{tests_playbook}"
|
||||
when:
|
||||
- ansible_distribution_major_version != '6'
|
||||
""" # noqa: E501 # ignore that the line is too long
|
||||
|
||||
|
||||
def get_current_provider_code():
|
||||
with open("../defaults/main.yml") as defaults:
|
||||
yaml_defaults = yaml.safe_load(defaults)
|
||||
current_provider = yaml_defaults["network_provider_current"]
|
||||
return current_provider
|
||||
|
||||
|
||||
def generate_nominal_other_playbook(tests_playbook):
|
||||
nominal_other_testfile_data = OTHER_PLAYBOOK.format(tests_playbook=tests_playbook)
|
||||
nominal = yaml.safe_load(nominal_other_testfile_data)
|
||||
nominal[0]["vars"]["network_provider_current"] = get_current_provider_code()
|
||||
return yaml.dump(nominal, default_flow_style=False, explicit_start=True, width=80)
|
||||
|
||||
|
||||
def main():
|
||||
testsfiles = glob.glob("tests_*.yml")
|
||||
missing = []
|
||||
returncode = 0
|
||||
|
||||
# Generate files when specified
|
||||
generate = bool(len(sys.argv) > 1 and sys.argv[1] == "generate")
|
||||
|
||||
if not testsfiles:
|
||||
print("ERROR: No tests found")
|
||||
returncode = 1
|
||||
|
||||
for filename in testsfiles:
|
||||
if filename.endswith(OTHER_PROVIDER_SUFFIX):
|
||||
continue
|
||||
|
||||
if filename in IGNORE:
|
||||
continue
|
||||
|
||||
fileroot = os.path.splitext(filename)[0]
|
||||
other_testfile = fileroot + OTHER_PROVIDER_SUFFIX
|
||||
nominal_other_testfile_data = generate_nominal_other_playbook(filename)
|
||||
|
||||
if generate:
|
||||
with open(other_testfile, "w") as ofile:
|
||||
ofile.write(nominal_other_testfile_data)
|
||||
|
||||
if other_testfile not in testsfiles and not generate:
|
||||
missing.append(filename)
|
||||
else:
|
||||
with open(other_testfile) as ifile:
|
||||
testdata = ifile.read()
|
||||
if testdata != nominal_other_testfile_data:
|
||||
print(
|
||||
"ERROR: Playbook does not match nominal value " + other_testfile
|
||||
)
|
||||
returncode = 1
|
||||
|
||||
if missing:
|
||||
print("ERROR: No tests for other provider found for:\n" + ", \n".join(missing))
|
||||
print("Try to generate them with '{} generate'".format(sys.argv[0]))
|
||||
returncode = 1
|
||||
|
||||
return returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
68
roles/linux-system-roles.network/tests/get-coverage.sh
Executable file
68
roles/linux-system-roles.network/tests/get-coverage.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#! /bin/bash
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if [ -n "${DEBUG}" ]
|
||||
then
|
||||
set -x
|
||||
fi
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 2 ]
|
||||
then
|
||||
echo "USAGE: ${0} host playbook"
|
||||
echo "Get coverage info from host for playbook"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
host="${1}"
|
||||
shift
|
||||
playbook="${1}"
|
||||
|
||||
coverage_data="remote-coveragedata-${host}-${playbook%.yml}"
|
||||
coverage="/root/.local/bin/coverage"
|
||||
|
||||
echo "Getting coverage for ${playbook} on ${host}" >&2
|
||||
|
||||
call_ansible() {
|
||||
local module="${1}"
|
||||
shift
|
||||
local args="${1}"
|
||||
shift
|
||||
ansible -m "${module}" -i "${host}", -a "${args}" all "${@}"
|
||||
}
|
||||
|
||||
remote_coverage_dir="$(mktemp -d /tmp/remote_coverage-XXXXXX)"
|
||||
trap "rm -rf '${remote_coverage_dir}'" EXIT
|
||||
ansible-playbook -i "${host}", get-coverage.yml -e "test_playbook=${playbook} destdir=${remote_coverage_dir}"
|
||||
|
||||
#COVERAGE_FILE=remote-coverage coverage combine remote-coverage/tests_*/*/root/.coverage
|
||||
./merge-coverage.sh coverage "${coverage_data}"-tmp $(find "${remote_coverage_dir}" -type f | tr , _)
|
||||
|
||||
# When https://github.com/nedbat/coveragepy/pull/49 is merged, this can be simplified:
|
||||
if false
|
||||
then
|
||||
cat > tmp_merge_coveragerc <<EOF
|
||||
[paths]
|
||||
source =
|
||||
.
|
||||
/tmp/ansible_*/
|
||||
EOF
|
||||
else
|
||||
cat > tmp_merge_coveragerc <<EOF
|
||||
[paths]
|
||||
source =
|
||||
.
|
||||
EOF
|
||||
for file in $(COVERAGE_FILE="${coverage_data}"-tmp coverage report | grep -o "/tmp/ansible_[^/]*" | sort -u)
|
||||
do
|
||||
echo " ${file}" >> tmp_merge_coveragerc
|
||||
done
|
||||
fi
|
||||
|
||||
COVERAGE_FILE="${coverage_data}" coverage combine --rcfile tmp_merge_coveragerc "${coverage_data}"-tmp
|
||||
rm tmp_merge_coveragerc
|
||||
|
||||
COVERAGE_FILE="${coverage_data}" coverage report ||:
|
||||
COVERAGE_FILE="${coverage_data}" coverage html --directory "htmlcov-${coverage_data}" ||:
|
||||
|
||||
echo "Coverage collected in: ${coverage_data}"
|
||||
66
roles/linux-system-roles.network/tests/get-coverage.yml
Normal file
66
roles/linux-system-roles.network/tests/get-coverage.yml
Normal file
@@ -0,0 +1,66 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
# This expects the variable test_playbook to be set from the outside
|
||||
- name: Prepare for coverage extraction
|
||||
hosts: all
|
||||
tasks:
|
||||
# Use set_fact to set variables to make them available in all plays
|
||||
# 'vars:' Would only set variables for the current play
|
||||
- name: set facts
|
||||
set_fact:
|
||||
coverage_module: network_connections
|
||||
coverage: /root/.local/bin/coverage
|
||||
destdir: "remote_coverage/{{ test_playbook }}"
|
||||
|
||||
# This uses variables from the other set_fact task, therefore it needs to
|
||||
# be its own task
|
||||
- name: set more facts
|
||||
set_fact:
|
||||
coverage_file: ansible-coverage-{{ coverage_module }}-{{ test_playbook|replace('.yml', '') }}
|
||||
|
||||
- name: debug info
|
||||
debug:
|
||||
msg: Getting coverage for '{{ coverage_module }}' with '{{ test_playbook }}'
|
||||
|
||||
# combine data in case old data is left there
|
||||
- command: "{{ coverage }} combine"
|
||||
environment:
|
||||
COVERAGE_FILE: "{{ coverage_file }}"
|
||||
ignore_errors: yes
|
||||
|
||||
- name: remove old data
|
||||
file:
|
||||
state: absent
|
||||
path: "{{ coverage_file }}"
|
||||
|
||||
- name: remove old data
|
||||
shell: rm -f .coverage.*
|
||||
|
||||
- name: copy coveragerc
|
||||
copy:
|
||||
content: "[run]\ndisable_warnings = no-data-collected\n"
|
||||
dest: .coveragerc
|
||||
|
||||
- name: install latest pip
|
||||
pip:
|
||||
name: coverage
|
||||
extra_args: --user --upgrade
|
||||
|
||||
- import_playbook: "{{ test_playbook }}"
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ coverage }} run -p --include *ansible_module_{{ coverage_module }}.py"
|
||||
|
||||
- name: Gather coverage data
|
||||
hosts: all
|
||||
tasks:
|
||||
- shell: "{{ coverage }} combine .coverage.*"
|
||||
environment:
|
||||
COVERAGE_FILE: "{{ coverage_file }}"
|
||||
|
||||
- name: Get coverage data
|
||||
hosts: all
|
||||
tasks:
|
||||
- fetch:
|
||||
src: "{{ coverage_file }}"
|
||||
dest: "{{ destdir }}"
|
||||
flat: no
|
||||
34
roles/linux-system-roles.network/tests/get-total-coverage.sh
Executable file
34
roles/linux-system-roles.network/tests/get-total-coverage.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#! /bin/bash
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
set -e
|
||||
coverage_data=total-coveragedata
|
||||
testhost="${1}"
|
||||
|
||||
if [ "$#" -lt 1 ]
|
||||
then
|
||||
echo "USAGE: ${0} host"
|
||||
echo "Get local and all remote coverage data for host"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f remote-coveragedata* "${coveragedata}"
|
||||
|
||||
|
||||
# collect pytest coverage
|
||||
tox -e py26,py27,py36,py37 -- --cov-append
|
||||
|
||||
for test_playbook in tests_*.yml
|
||||
do
|
||||
./get-coverage.sh "${testhost}" "${test_playbook}"
|
||||
done
|
||||
|
||||
./merge-coverage.sh coverage "total-remote-coveragedata" remote-coveragedata-*
|
||||
./covstats .coverage remote-coveragedata-* "total-remote-coveragedata"
|
||||
|
||||
./merge-coverage.sh coverage "${coverage_data}" .coverage remote-coveragedata-*
|
||||
echo "Total coverage:"
|
||||
COVERAGE_FILE="${coverage_data}" coverage report ||:
|
||||
COVERAGE_FILE="${coverage_data}" coverage html --directory "htmlcov-${coverage_data}" ||:
|
||||
echo "Open HTML report with:"
|
||||
echo "xdg-open htmlcov-${coverage_data}/index.html"
|
||||
35
roles/linux-system-roles.network/tests/merge-coverage.sh
Executable file
35
roles/linux-system-roles.network/tests/merge-coverage.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#! /bin/bash
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if [ -n "${DEBUG}" ]
|
||||
then
|
||||
set -x
|
||||
fi
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 3 ]
|
||||
then
|
||||
echo "USAGE: ${0} path_to_coverage_binary output_file input_files..."
|
||||
echo "Merges all input_files into output file without removing input_files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# path to coverage binary
|
||||
coverage="${1}"
|
||||
shift
|
||||
|
||||
# read by coverage binary
|
||||
export COVERAGE_FILE="${1}"
|
||||
shift
|
||||
|
||||
tempdir="$(mktemp -d /tmp/coverage_merge-XXXXXX)"
|
||||
trap "rm -rf '${tempdir}'" EXIT
|
||||
|
||||
cp --backup=numbered -- "${@}" "${tempdir}"
|
||||
# FIXME: Would not work if coverage files are not hidden but they are by
|
||||
# default
|
||||
shopt -s dotglob
|
||||
"${coverage}" combine "${tempdir}/"*
|
||||
|
||||
echo "Merged data into ${COVERAGE_FILE}"
|
||||
./covstats "${COVERAGE_FILE}"
|
||||
1
roles/linux-system-roles.network/tests/playbooks/roles
Symbolic link
1
roles/linux-system-roles.network/tests/playbooks/roles
Symbolic link
@@ -0,0 +1 @@
|
||||
../roles/
|
||||
1
roles/linux-system-roles.network/tests/playbooks/tasks
Symbolic link
1
roles/linux-system-roles.network/tests/playbooks/tasks
Symbolic link
@@ -0,0 +1 @@
|
||||
../tasks/
|
||||
@@ -0,0 +1,111 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
interface: lsrfeat1
|
||||
type: veth
|
||||
tasks:
|
||||
- name: "INIT: Ethtool feeatures tests"
|
||||
debug:
|
||||
msg: "##################################################"
|
||||
- include_tasks: tasks/show-interfaces.yml
|
||||
- include_tasks: tasks/manage-test-interface.yml
|
||||
vars:
|
||||
state: present
|
||||
- include_tasks: tasks/assert-device_present.yml
|
||||
- name: Install ethtool (test dependency)
|
||||
package:
|
||||
name: ethtool
|
||||
state: present
|
||||
- block:
|
||||
- name: "TEST: I can create a profile without changing the ethtool features."
|
||||
debug:
|
||||
msg: "##################################################"
|
||||
- name: Get current device features
|
||||
command: "ethtool --show-features {{ interface }}"
|
||||
register: original_ethtool_features
|
||||
- import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
ip:
|
||||
dhcp4: "no"
|
||||
auto6: "no"
|
||||
- name: Get current device features
|
||||
command: "ethtool --show-features {{ interface }}"
|
||||
register: ethtool_features
|
||||
- name: "ASSERT: The profile does not change the ethtool features"
|
||||
assert:
|
||||
that:
|
||||
- original_ethtool_features.stdout == ethtool_features.stdout
|
||||
- name: "TEST: I can disable gro and tx-tcp-segmentation and enable gso."
|
||||
debug:
|
||||
msg: "##################################################"
|
||||
- import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
ip:
|
||||
dhcp4: "no"
|
||||
auto6: "no"
|
||||
ethtool:
|
||||
features:
|
||||
gro: "no"
|
||||
gso: "yes"
|
||||
tx-tcp-segmentation: "no"
|
||||
- name: Get current device features
|
||||
command: "ethtool --show-features {{ interface }}"
|
||||
register: ethtool_features
|
||||
- name:
|
||||
debug:
|
||||
var: ethtool_features.stdout_lines
|
||||
- name: Assert device features
|
||||
assert:
|
||||
that:
|
||||
- "'generic-receive-offload: off' in ethtool_features.stdout_lines"
|
||||
- "'generic-segmentation-offload: on' in ethtool_features.stdout_lines"
|
||||
- "'tx-tcp-segmentation: off' in ethtool_features.stdout_lines | map('trim')"
|
||||
- name: "TEST: I can reset features to their original value."
|
||||
debug:
|
||||
msg: "##################################################"
|
||||
- import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
ip:
|
||||
dhcp4: "no"
|
||||
auto6: "no"
|
||||
- name: Get current device features
|
||||
command: "ethtool --show-features {{ interface }}"
|
||||
register: ethtool_features
|
||||
# Resetting the ethtools only works with NetworkManager
|
||||
- name: "ASSERT: The profile does not change the ethtool features"
|
||||
assert:
|
||||
that:
|
||||
- original_ethtool_features.stdout == ethtool_features.stdout
|
||||
when:
|
||||
network_provider == 'nm'
|
||||
always:
|
||||
- block:
|
||||
- import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
persistent_state: absent
|
||||
state: down
|
||||
ignore_errors: true
|
||||
- include_tasks: tasks/manage-test-interface.yml
|
||||
vars:
|
||||
state: absent
|
||||
tags:
|
||||
- "tests::cleanup"
|
||||
@@ -0,0 +1,49 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
interface: statebr
|
||||
profile: "{{ interface }}"
|
||||
network_provider: nm
|
||||
tasks:
|
||||
- debug:
|
||||
msg: Inside states tests
|
||||
- include_tasks: tasks/show-interfaces.yml
|
||||
- include_tasks: tasks/assert-device_absent.yml
|
||||
|
||||
# create test profile
|
||||
- include_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: statebr
|
||||
state: up
|
||||
type: bridge
|
||||
ip:
|
||||
dhcp4: false
|
||||
auto6: false
|
||||
- include_tasks: tasks/assert-device_present.yml
|
||||
- include_tasks: tasks/assert-profile_present.yml
|
||||
|
||||
# test case (remove profile but keep it up)
|
||||
# I can remove a profile but keep the configuration active.
|
||||
- include_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: statebr
|
||||
persistent_state: absent
|
||||
- include_tasks: tasks/assert-device_present.yml
|
||||
- include_tasks: tasks/assert-profile_absent.yml
|
||||
|
||||
# test case
|
||||
# I can set a profile down that is up and absent.
|
||||
- name: Set down
|
||||
include_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: statebr
|
||||
state: down
|
||||
- include_tasks: tasks/assert-device_absent.yml
|
||||
- include_tasks: tasks/assert-profile_absent.yml
|
||||
@@ -0,0 +1,66 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
type: veth
|
||||
interface: lsr101
|
||||
vlan_interface: lsr101.90
|
||||
tasks:
|
||||
- include_tasks: tasks/show-interfaces.yml
|
||||
- include_tasks: tasks/manage-test-interface.yml
|
||||
vars:
|
||||
state: present
|
||||
- include_tasks: tasks/assert-device_present.yml
|
||||
- name: "TEST: I can configure the MTU for a vlan interface without autoconnect."
|
||||
debug:
|
||||
msg: "##################################################"
|
||||
- import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
type: ethernet
|
||||
state: up
|
||||
mtu: 1492
|
||||
autoconnect: false
|
||||
ip:
|
||||
dhcp4: false
|
||||
auto6: false
|
||||
|
||||
- name: "{{ vlan_interface }}"
|
||||
parent: "{{ interface }}"
|
||||
type: vlan
|
||||
vlan_id: 90
|
||||
mtu: 1280
|
||||
state: up
|
||||
autoconnect: false
|
||||
ip:
|
||||
dhcp4: false
|
||||
auto6: false
|
||||
- include_tasks: tasks/assert-device_present.yml
|
||||
vars:
|
||||
interface: "{{ vlan_interface }}"
|
||||
- include_tasks: tasks/assert-profile_present.yml
|
||||
vars:
|
||||
profile: "{{ item }}"
|
||||
loop:
|
||||
- "{{ interface }}"
|
||||
- "{{ vlan_interface }}"
|
||||
|
||||
- name: "TEARDOWN: remove profiles."
|
||||
debug:
|
||||
msg: "##################################################"
|
||||
- import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
persistent_state: absent
|
||||
state: down
|
||||
- name: "{{ vlan_interface }}"
|
||||
persistent_state: absent
|
||||
state: down
|
||||
ignore_errors: true
|
||||
- include_tasks: tasks/manage-test-interface.yml
|
||||
vars:
|
||||
state: absent
|
||||
10
roles/linux-system-roles.network/tests/remove-profile.yml
Normal file
10
roles/linux-system-roles.network/tests/remove-profile.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: Remove {{ profile }}
|
||||
hosts: all
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ profile }}"
|
||||
persistent_state: absent
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
@@ -0,0 +1 @@
|
||||
../../../defaults/
|
||||
@@ -0,0 +1 @@
|
||||
../../../library/
|
||||
@@ -0,0 +1 @@
|
||||
../../../meta/
|
||||
@@ -0,0 +1 @@
|
||||
../../../module_utils/
|
||||
@@ -0,0 +1 @@
|
||||
../../../tasks/
|
||||
6
roles/linux-system-roles.network/tests/run-tasks.yml
Normal file
6
roles/linux-system-roles.network/tests/run-tasks.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: Run the tasklist {{ task }}
|
||||
hosts: all
|
||||
tasks:
|
||||
- include_tasks: "{{ task }}"
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- include: get-interface_stat.yml
|
||||
- name: "assert that interface {{ interface }} is absent"
|
||||
assert:
|
||||
that: not interface_stat.stat.exists
|
||||
msg: "{{ interface }} exists"
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- include: get-interface_stat.yml
|
||||
- name: "assert that interface {{ interface }} is present"
|
||||
assert:
|
||||
that: interface_stat.stat.exists
|
||||
msg: "{{ interface }} does not exist"
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- include: get-profile_stat.yml
|
||||
- name: "assert that profile '{{ profile }}' is absent"
|
||||
assert:
|
||||
that: not profile_stat.stat.exists
|
||||
msg: "profile {{ profile_path }} does exist"
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- include: get-profile_stat.yml
|
||||
- name: "assert that profile '{{ profile }}' is present"
|
||||
assert:
|
||||
that: profile_stat.stat.exists
|
||||
msg: "profile {{ profile_path }} does not exist"
|
||||
@@ -0,0 +1,20 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- include_tasks: show-interfaces.yml
|
||||
- include_tasks: manage-test-interface.yml
|
||||
vars:
|
||||
state: absent
|
||||
- include_tasks: show-interfaces.yml
|
||||
- include_tasks: assert-device_absent.yml
|
||||
|
||||
- include_tasks: manage-test-interface.yml
|
||||
vars:
|
||||
state: present
|
||||
- include_tasks: show-interfaces.yml
|
||||
- include_tasks: assert-device_present.yml
|
||||
|
||||
- include_tasks: manage-test-interface.yml
|
||||
vars:
|
||||
state: absent
|
||||
- include_tasks: show-interfaces.yml
|
||||
- include_tasks: assert-device_absent.yml
|
||||
@@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- command: ls -1
|
||||
args:
|
||||
chdir: /sys/class/net
|
||||
register: _current_interfaces
|
||||
- set_fact:
|
||||
current_interfaces: "{{ _current_interfaces.stdout_lines }}"
|
||||
@@ -0,0 +1,9 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: "Get stat for interface {{ interface }}"
|
||||
stat:
|
||||
get_attributes: false
|
||||
get_checksum: false
|
||||
get_mime: false
|
||||
path: "/sys/class/net/{{ interface }}"
|
||||
register: interface_stat
|
||||
@@ -0,0 +1,26 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: "Get stat for network-scripts"
|
||||
stat:
|
||||
get_attributes: false
|
||||
get_checksum: false
|
||||
get_mime: false
|
||||
path: "/etc/sysconfig/network-scripts"
|
||||
register: network_scripts_stat
|
||||
- name: Set profile path (network-scripts)
|
||||
set_fact:
|
||||
profile_path: /etc/sysconfig/network-scripts/ifcfg-{{ profile }}
|
||||
when:
|
||||
- network_scripts_stat.stat.exists
|
||||
- name: Set profile path (NetworkManager system-connections)
|
||||
set_fact:
|
||||
profile_path: /etc/NetworkManager/system-connections/{{ profile }}
|
||||
when:
|
||||
- not network_scripts_stat.stat.exists
|
||||
- name: stat profile file
|
||||
stat:
|
||||
get_attributes: false
|
||||
get_checksum: false
|
||||
get_mime: false
|
||||
path: "{{ profile_path }}"
|
||||
register: profile_stat
|
||||
@@ -0,0 +1,50 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- fail:
|
||||
msg: "state needs to be present or absent, not '{{ state }}'"
|
||||
when: state not in ["present", "absent"]
|
||||
|
||||
- fail:
|
||||
msg: "type needs to be dummy, tap or veth, not '{{ type }}'"
|
||||
when: type not in ["dummy", "tap", "veth"]
|
||||
|
||||
# - include: get-current_interfaces.yml
|
||||
- include: show-interfaces.yml
|
||||
|
||||
- name: Install iproute
|
||||
package:
|
||||
name: iproute
|
||||
state: present
|
||||
|
||||
# veth
|
||||
- name: Create veth interface {{ interface }}
|
||||
shell: ip link add {{ interface }} type veth peer name peer{{ interface }}
|
||||
when: "type == 'veth' and state == 'present' and
|
||||
interface not in current_interfaces"
|
||||
|
||||
- name: Delete veth interface {{ interface }}
|
||||
shell: ip link del {{ interface }} type veth
|
||||
when: "type == 'veth' and state == 'absent' and
|
||||
interface in current_interfaces"
|
||||
|
||||
# dummy
|
||||
- name: Create dummy interface {{ interface }}
|
||||
shell: ip link add "{{ interface }}" type dummy
|
||||
when: "type == 'dummy' and state == 'present' and
|
||||
interface not in current_interfaces"
|
||||
|
||||
- name: Delete dummy interface {{ interface }}
|
||||
shell: ip link del "{{ interface }}" type dummy
|
||||
when: "type == 'dummy' and state == 'absent' and
|
||||
interface in current_interfaces"
|
||||
|
||||
# tap
|
||||
- name: Create tap interface {{ interface }}
|
||||
shell: ip tuntap add dev {{ interface }} mode tap
|
||||
when: "type == 'tap' and state == 'present'
|
||||
and interface not in current_interfaces"
|
||||
|
||||
- name: Delete tap interface {{ interface }}
|
||||
shell: ip tuntap del dev {{ interface }} mode tap
|
||||
when: "type == 'tap' and state == 'absent' and
|
||||
interface in current_interfaces"
|
||||
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- include: get-current_interfaces.yml
|
||||
- debug:
|
||||
msg: "current_interfaces: {{ current_interfaces }}"
|
||||
55
roles/linux-system-roles.network/tests/tests_bridge.yml
Normal file
55
roles/linux-system-roles.network/tests/tests_bridge.yml
Normal file
@@ -0,0 +1,55 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: Test configuring bridges
|
||||
hosts: all
|
||||
vars:
|
||||
interface: LSR-TST-br31
|
||||
|
||||
tasks:
|
||||
- name: "set interface={{ interface }}"
|
||||
set_fact:
|
||||
interface: "{{ interface }}"
|
||||
- include_tasks: tasks/show-interfaces.yml
|
||||
- include_tasks: tasks/assert-device_absent.yml
|
||||
|
||||
- name: Add test bridge
|
||||
hosts: all
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
interface_name: "{{ interface }}"
|
||||
state: up
|
||||
type: bridge
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: yes
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
|
||||
- import_playbook: run-tasks.yml
|
||||
vars:
|
||||
task: tasks/assert-device_present.yml
|
||||
|
||||
- import_playbook: run-tasks.yml
|
||||
vars:
|
||||
profile: "{{ interface }}"
|
||||
task: tasks/assert-profile_present.yml
|
||||
|
||||
- import_playbook: down-profile.yml
|
||||
vars:
|
||||
profile: "{{ interface }}"
|
||||
# FIXME: assert profile/device down
|
||||
|
||||
- import_playbook: remove-profile.yml
|
||||
vars:
|
||||
profile: "{{ interface }}"
|
||||
|
||||
- import_playbook: run-tasks.yml
|
||||
vars:
|
||||
profile: "{{ interface }}"
|
||||
task: tasks/assert-profile_absent.yml
|
||||
|
||||
# FIXME: Devices might still be left when profile is absent
|
||||
#- import_playbook: run-tasks.yml
|
||||
# vars:
|
||||
# task: tasks/assert-device_absent.yml
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- hosts: all
|
||||
name: Run playbook 'tests_bridge.yml' with non-default provider
|
||||
tasks:
|
||||
- name: Get service facts
|
||||
service_facts: null
|
||||
- name: Set network provider
|
||||
set_fact:
|
||||
network_provider: '{{ "initscripts" if network_provider_current == "nm" else
|
||||
"nm" }}'
|
||||
vars:
|
||||
network_provider_current: '{{ ''nm'' if ''NetworkManager.service'' in ansible_facts.services
|
||||
and ansible_facts.services[''NetworkManager.service''][''state''] == ''running''
|
||||
else ''initscripts'' }}'
|
||||
- import_playbook: tests_bridge.yml
|
||||
when:
|
||||
- ansible_distribution_major_version != '6'
|
||||
6
roles/linux-system-roles.network/tests/tests_default.yml
Normal file
6
roles/linux-system-roles.network/tests/tests_default.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: Test executing the role with default parameters
|
||||
hosts: all
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- hosts: all
|
||||
name: Run playbook 'tests_default.yml' with non-default provider
|
||||
tasks:
|
||||
- name: Get service facts
|
||||
service_facts: null
|
||||
- name: Set network provider
|
||||
set_fact:
|
||||
network_provider: '{{ "initscripts" if network_provider_current == "nm" else
|
||||
"nm" }}'
|
||||
vars:
|
||||
network_provider_current: '{{ ''nm'' if ''NetworkManager.service'' in ansible_facts.services
|
||||
and ansible_facts.services[''NetworkManager.service''][''state''] == ''running''
|
||||
else ''initscripts'' }}'
|
||||
- import_playbook: tests_default.yml
|
||||
when:
|
||||
- ansible_distribution_major_version != '6'
|
||||
62
roles/linux-system-roles.network/tests/tests_ethernet.yml
Normal file
62
roles/linux-system-roles.network/tests/tests_ethernet.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- debug:
|
||||
msg: Inside ethernet tests
|
||||
- debug:
|
||||
var: network_provider
|
||||
|
||||
- name: Test configuring ethernet devices
|
||||
hosts: all
|
||||
vars:
|
||||
type: veth
|
||||
interface: lsr27
|
||||
|
||||
tasks:
|
||||
- name: "set type={{ type }} and interface={{ interface }}"
|
||||
set_fact:
|
||||
type: "{{ type }}"
|
||||
interface: "{{ interface }}"
|
||||
- include_tasks: tasks/show-interfaces.yml
|
||||
- include_tasks: tasks/manage-test-interface.yml
|
||||
vars:
|
||||
state: present
|
||||
- include_tasks: tasks/assert-device_present.yml
|
||||
|
||||
- name: Test static interface up
|
||||
hosts: all
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
interface_name: "{{ interface }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
autoconnect: yes
|
||||
ip:
|
||||
address: 192.0.2.1/24
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
|
||||
- hosts: all
|
||||
tasks:
|
||||
- debug:
|
||||
var: network_provider
|
||||
|
||||
# FIXME: assert profile present
|
||||
# FIXME: assert profile/device up + IP address
|
||||
- import_playbook: down-profile.yml
|
||||
vars:
|
||||
profile: "{{ interface }}"
|
||||
# FIXME: assert profile/device down
|
||||
- import_playbook: remove-profile.yml
|
||||
vars:
|
||||
profile: "{{ interface }}"
|
||||
# FIXME: assert profile away
|
||||
- name: Remove interfaces
|
||||
hosts: all
|
||||
tasks:
|
||||
- include_tasks: tasks/manage-test-interface.yml
|
||||
vars:
|
||||
state: absent
|
||||
- include_tasks: tasks/assert-device_absent.yml
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- hosts: all
|
||||
name: Run playbook 'tests_ethernet.yml' with non-default provider
|
||||
tasks:
|
||||
- name: Get service facts
|
||||
service_facts: null
|
||||
- name: Set network provider
|
||||
set_fact:
|
||||
network_provider: '{{ "initscripts" if network_provider_current == "nm" else
|
||||
"nm" }}'
|
||||
vars:
|
||||
network_provider_current: '{{ ''nm'' if ''NetworkManager.service'' in ansible_facts.services
|
||||
and ansible_facts.services[''NetworkManager.service''][''state''] == ''running''
|
||||
else ''initscripts'' }}'
|
||||
- import_playbook: tests_ethernet.yml
|
||||
when:
|
||||
- ansible_distribution_major_version != '6'
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
# set network provider and gather facts
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Set network provider to 'initscripts'
|
||||
set_fact:
|
||||
network_provider: initscripts
|
||||
|
||||
# workaround for: https://github.com/ansible/ansible/issues/27973
|
||||
# There is no way in Ansible to abort a playbook hosts with specific OS
|
||||
# releases Therefore we include the playbook with the tests only if the hosts
|
||||
# would support it.
|
||||
- import_playbook: playbooks/tests_ethtool_features.yml
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
# set network provider and gather facts
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Set network provider to 'nm'
|
||||
set_fact:
|
||||
network_provider: nm
|
||||
- name: Install NetworkManager
|
||||
package:
|
||||
name: NetworkManager
|
||||
state: present
|
||||
- name: Get NetworkManager version
|
||||
command: rpm -q --qf "%{version}" NetworkManager
|
||||
args:
|
||||
warn: "no"
|
||||
when: true
|
||||
register: NetworkManager_version
|
||||
|
||||
# workaround for: https://github.com/ansible/ansible/issues/27973
|
||||
# There is no way in Ansible to abort a playbook hosts with specific OS
|
||||
# releases Therefore we include the playbook with the tests only if the hosts
|
||||
# would support it.
|
||||
# The test should run with NetworkManager, therefore it cannot run on RHEL 6 or CentOS 6.
|
||||
- import_playbook: playbooks/tests_ethtool_features.yml
|
||||
when:
|
||||
- ansible_distribution_major_version != '6'
|
||||
# NetworkManager 1.20.0 introduced ethtool settings support
|
||||
- NetworkManager_version.stdout is version('1.20.0', '>=')
|
||||
@@ -0,0 +1,27 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: Check that creating and removing test devices and assertions work
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: test veth interface management
|
||||
include_tasks: tasks/create-and-remove-interface.yml
|
||||
vars:
|
||||
type: veth
|
||||
interface: veth1298
|
||||
|
||||
- name: test veth interface management
|
||||
include_tasks: tasks/create-and-remove-interface.yml
|
||||
vars:
|
||||
type: dummy
|
||||
interface: dummy1298
|
||||
|
||||
# FIXME: when: does not seem to work with include_tasks, therefore this cannot be safely tested for now
|
||||
# - name: test tap interfaces
|
||||
# include_tasks: tasks/create-and-remove-interface.yml
|
||||
# vars:
|
||||
# - type: tap
|
||||
# - interface: tap1298
|
||||
# when: ansible_distribution_major_version > 6
|
||||
# # ip tuntap does not exist on RHEL6
|
||||
# # FIXME: Maybe use some other tool to manage devices, openvpn can do this,
|
||||
# # but it is in EPEL
|
||||
11
roles/linux-system-roles.network/tests/tests_states.yml
Normal file
11
roles/linux-system-roles.network/tests/tests_states.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
# empty playbook to gather facts for import_playbook when clause
|
||||
- hosts: all
|
||||
|
||||
# workaround for: https://github.com/ansible/ansible/issues/27973
|
||||
# There is no way in Ansible to abort a playbook hosts with specific OS
|
||||
# releases Therefore we include the playbook with the tests only if the hosts
|
||||
# would support it.
|
||||
# The test requires NetworkManager, therefore it cannot run on RHEL 6 or CentOS 6.
|
||||
- import_playbook: playbooks/tests_states.yml
|
||||
when: ansible_distribution_major_version != '6'
|
||||
89
roles/linux-system-roles.network/tests/tests_unit.yml
Normal file
89
roles/linux-system-roles.network/tests/tests_unit.yml
Normal file
@@ -0,0 +1,89 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
name: Setup for test running
|
||||
tasks:
|
||||
- name: Install EPEL on enterprise Linux for python2-mock
|
||||
command: yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm
|
||||
args:
|
||||
warn: false
|
||||
creates: /etc/yum.repos.d/epel.repo
|
||||
when:
|
||||
- ansible_distribution in ['RedHat', 'CentOS']
|
||||
- ansible_distribution_major_version in ['6', '7']
|
||||
|
||||
- name: Install dependencies
|
||||
package:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
# Ignore error because some package names might not be available
|
||||
ignore_errors: true
|
||||
loop:
|
||||
- NetworkManager-libnm
|
||||
- python2-gobject-base
|
||||
- python3-gobject-base
|
||||
- python-gobject-base
|
||||
- python2-mock
|
||||
|
||||
- hosts: all
|
||||
name: execute python unit tests
|
||||
tasks:
|
||||
- name: Copy python modules
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: /tmp/test-unit-1/
|
||||
local_follow: false
|
||||
loop:
|
||||
- ../library/network_connections.py
|
||||
- unit/test_network_connections.py
|
||||
- ../module_utils/network_lsr
|
||||
|
||||
- name: Create helpers directory
|
||||
file:
|
||||
state: directory
|
||||
dest: /tmp/test-unit-1/helpers
|
||||
|
||||
- name: Copy helpers
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: /tmp/test-unit-1/helpers
|
||||
mode: 0755
|
||||
with_fileglob:
|
||||
- unit/helpers/*
|
||||
|
||||
- name: Check if python2 is available
|
||||
command: python2 --version
|
||||
ignore_errors: true
|
||||
register: python2_available
|
||||
when: true
|
||||
|
||||
- name: Run python2 unit tests
|
||||
command: python2 /tmp/test-unit-1/test_network_connections.py --verbose
|
||||
when: python2_available is succeeded
|
||||
register: python2_result
|
||||
|
||||
- name: Check if python3 is available
|
||||
command: python3 --version
|
||||
ignore_errors: true
|
||||
register: python3_available
|
||||
when: true
|
||||
|
||||
- name: Run python3 unit tests
|
||||
command: python3 /tmp/test-unit-1/test_network_connections.py --verbose
|
||||
when: python3_available is succeeded
|
||||
register: python3_result
|
||||
|
||||
- name: Show python2 unit test results
|
||||
debug:
|
||||
var: python2_result.stderr_lines
|
||||
when: python2_result is succeeded
|
||||
|
||||
- name: Show python3 unit test results
|
||||
debug:
|
||||
var: python3_result.stderr_lines
|
||||
when: python3_result is succeeded
|
||||
|
||||
- name: Ensure that at least one python unit test ran
|
||||
fail:
|
||||
msg: Tests did not run with python2 or python3
|
||||
when: not (python2_available is succeeded or python3_available is succeeded)
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
# set network provider and gather facts
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Set network provider to 'initscripts'
|
||||
set_fact:
|
||||
network_provider: initscripts
|
||||
|
||||
# workaround for: https://github.com/ansible/ansible/issues/27973
|
||||
# There is no way in Ansible to abort a playbook hosts with specific OS
|
||||
# releases Therefore we include the playbook with the tests only if the hosts
|
||||
# would support it.
|
||||
- import_playbook: playbooks/tests_vlan_mtu.yml
|
||||
15
roles/linux-system-roles.network/tests/tests_vlan_mtu_nm.yml
Normal file
15
roles/linux-system-roles.network/tests/tests_vlan_mtu_nm.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
# set network provider and gather facts
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Set network provider to 'nm'
|
||||
set_fact:
|
||||
network_provider: nm
|
||||
|
||||
# workaround for: https://github.com/ansible/ansible/issues/27973
|
||||
# There is no way in Ansible to abort a playbook hosts with specific OS
|
||||
# releases Therefore we include the playbook with the tests only if the hosts
|
||||
# would support it.
|
||||
# The test requires NetworkManager, therefore it cannot run on RHEL 6 or CentOS 6.
|
||||
- import_playbook: playbooks/tests_vlan_mtu.yml
|
||||
when: ansible_distribution_major_version != '6'
|
||||
6
roles/linux-system-roles.network/tests/unit/helpers/ethtool
Executable file
6
roles/linux-system-roles.network/tests/unit/helpers/ethtool
Executable file
@@ -0,0 +1,6 @@
|
||||
#! /bin/bash
|
||||
|
||||
if [ "${1}" == "-P" ] && [ "${2}" != "" ]
|
||||
then
|
||||
echo "Permanent address: 23:00:00:00:00:00"
|
||||
fi
|
||||
2236
roles/linux-system-roles.network/tests/unit/test_network_connections.py
Executable file
2236
roles/linux-system-roles.network/tests/unit/test_network_connections.py
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
""" Tests for network_connections Ansible module """
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
TESTS_BASEDIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(1, os.path.join(TESTS_BASEDIR, "../..", "library"))
|
||||
sys.path.insert(1, os.path.join(TESTS_BASEDIR, "../..", "module_utils"))
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError: # py2
|
||||
import mock
|
||||
|
||||
sys.modules["ansible"] = mock.Mock()
|
||||
sys.modules["ansible.module_utils.basic"] = mock.Mock()
|
||||
sys.modules["ansible.module_utils"] = mock.Mock()
|
||||
sys.modules["ansible.module_utils.network_lsr"] = __import__("network_lsr")
|
||||
|
||||
with mock.patch.dict("sys.modules", {"gi": mock.Mock(), "gi.repository": mock.Mock()}):
|
||||
# pylint: disable=import-error, wrong-import-position
|
||||
from network_lsr import nm_provider
|
||||
|
||||
|
||||
def test_get_nm_ethtool_feature():
|
||||
""" Test get_nm_ethtool_feature() """
|
||||
with mock.patch.object(nm_provider.Util, "NM") as nm_mock:
|
||||
nm_feature = nm_provider.get_nm_ethtool_feature("esp-hw-offload")
|
||||
assert nm_feature == nm_mock.return_value.ETHTOOL_OPTNAME_FEATURE_ESP_HW_OFFLOAD
|
||||
181
roles/linux-system-roles.network/tox.ini
Normal file
181
roles/linux-system-roles.network/tox.ini
Normal file
@@ -0,0 +1,181 @@
|
||||
[tox]
|
||||
envlist = black, flake8, pylint, py{26,27,36,37}, ensure_non_running_provider
|
||||
skipsdist = true
|
||||
skip_missing_interpreters = True
|
||||
|
||||
[testenv]
|
||||
basepython = python3
|
||||
deps =
|
||||
py{26,27,36,37,38}: pytest-cov
|
||||
py{27,36,37,38}: pytest>=3.5.1
|
||||
py{26,27}: mock
|
||||
py26: pytest
|
||||
molecule_{lint,syntax,test}: docker
|
||||
molecule_{lint,syntax,test}: jmespath
|
||||
molecule_{lint,syntax,test}: molecule
|
||||
# The selinux pypi shim does not work with Ubuntu (as used by Travis), yet.
|
||||
# Therefore use a fork with Ubuntu support. This can be changed once the
|
||||
# update is available on PyPi.
|
||||
# molecule_{lint,syntax,test}: selinux
|
||||
molecule_{lint,syntax,test}: git+https://github.com/tyll/selinux-pypi-shim@fulllocation
|
||||
|
||||
[base]
|
||||
passenv = *
|
||||
setenv =
|
||||
PYTHONPATH = {toxinidir}/library:{toxinidir}/module_utils
|
||||
LC_ALL = C
|
||||
changedir = {toxinidir}/tests
|
||||
covtarget = {toxinidir}/library --cov {toxinidir}/module_utils
|
||||
pytesttarget = .
|
||||
|
||||
[testenv:black]
|
||||
deps = black
|
||||
|
||||
commands = black --check --diff --include "^[^.].*\.py$" .
|
||||
|
||||
[testenv:py26]
|
||||
install_command = pip install {opts} {packages}
|
||||
list_dependencies_command = pip freeze
|
||||
basepython = python2.6
|
||||
passenv = {[base]passenv}
|
||||
setenv =
|
||||
{[base]setenv}
|
||||
changedir = {[base]changedir}
|
||||
commands =
|
||||
pytest \
|
||||
--durations=5 \
|
||||
--cov={[base]covtarget} \
|
||||
--cov-report=html:htmlcov-py26 --cov-report=term \
|
||||
{posargs} \
|
||||
{[base]pytesttarget}
|
||||
|
||||
[testenv:py27]
|
||||
basepython = python2.7
|
||||
passenv = {[base]passenv}
|
||||
setenv =
|
||||
{[base]setenv}
|
||||
changedir = {[base]changedir}
|
||||
commands =
|
||||
pytest \
|
||||
--durations=5 \
|
||||
--cov={[base]covtarget} \
|
||||
--cov-report=html:htmlcov-py27 --cov-report=term \
|
||||
{posargs} \
|
||||
{[base]pytesttarget}
|
||||
|
||||
[testenv:py36]
|
||||
basepython = python3.6
|
||||
passenv = {[base]passenv}
|
||||
setenv =
|
||||
{[base]setenv}
|
||||
changedir = {[base]changedir}
|
||||
commands =
|
||||
pytest \
|
||||
--durations=5 \
|
||||
--cov={[base]covtarget} \
|
||||
--cov-report=html:htmlcov-py36 --cov-report=term \
|
||||
{posargs} \
|
||||
{[base]pytesttarget}
|
||||
|
||||
[testenv:py37]
|
||||
basepython = python3.7
|
||||
passenv = {[base]passenv}
|
||||
setenv =
|
||||
{[base]setenv}
|
||||
changedir = {[base]changedir}
|
||||
commands =
|
||||
pytest \
|
||||
--durations=5 \
|
||||
--cov={[base]covtarget} \
|
||||
--cov-report=html:htmlcov-py37 --cov-report=term \
|
||||
{posargs} \
|
||||
{[base]pytesttarget}
|
||||
|
||||
[testenv:py38]
|
||||
passenv = {[base]passenv}
|
||||
setenv =
|
||||
{[base]setenv}
|
||||
changedir = {[base]changedir}
|
||||
basepython = python3.8
|
||||
commands =
|
||||
pytest \
|
||||
--durations=5 \
|
||||
--cov={[base]covtarget} \
|
||||
--cov-report=html:htmlcov-py38 --cov-report=term \
|
||||
{posargs} \
|
||||
{[base]pytesttarget}
|
||||
|
||||
[testenv:pylint]
|
||||
basepython = python2.7
|
||||
setenv =
|
||||
{[base]setenv}
|
||||
deps =
|
||||
pylint>=1.8.4
|
||||
ansible
|
||||
commands =
|
||||
pylint \
|
||||
--errors-only \
|
||||
{posargs} \
|
||||
library/network_connections.py \
|
||||
module_utils/network_lsr \
|
||||
tests/unit/test_network_connections.py
|
||||
|
||||
[testenv:flake8]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
flake8>=3.5
|
||||
whitelist_externals = flake8
|
||||
commands=
|
||||
flake8 --statistics {posargs} \
|
||||
.
|
||||
|
||||
[testenv:coveralls]
|
||||
basepython = python2.7
|
||||
passenv = TRAVIS TRAVIS_*
|
||||
deps =
|
||||
coveralls
|
||||
changedir = {[base]changedir}
|
||||
commands =
|
||||
coveralls
|
||||
|
||||
[testenv:ensure_non_running_provider]
|
||||
deps =
|
||||
PyYAML
|
||||
changedir = {toxinidir}/tests
|
||||
commands = {toxinidir}/tests/ensure_non_running_provider.py
|
||||
|
||||
[testenv:molecule_lint]
|
||||
commands_pre =
|
||||
molecule --version
|
||||
ansible --version
|
||||
commands = molecule {posargs} lint
|
||||
|
||||
[testenv:molecule_syntax]
|
||||
commands = molecule {posargs} syntax
|
||||
|
||||
[testenv:molecule_test]
|
||||
commands = molecule {posargs} test
|
||||
|
||||
[pytest]
|
||||
addopts = -rxs
|
||||
|
||||
[flake8]
|
||||
show_source = True
|
||||
max-line-length = 88
|
||||
ignore = E402,W503
|
||||
|
||||
[pylint]
|
||||
max-line-length = 88
|
||||
disable = wrong-import-position
|
||||
|
||||
[pycodestyle]
|
||||
max-line-length = 88
|
||||
|
||||
[travis]
|
||||
python =
|
||||
2.6: py26
|
||||
2.7: py27,coveralls,flake8,pylint
|
||||
3.5: molecule_lint,molecule_syntax,molecule_test
|
||||
3.6: py36,black,ensure_non_running_provider
|
||||
3.7: py37
|
||||
3.8: py38
|
||||
Reference in New Issue
Block a user