library: let the module handle all connections at once

Instead, of having the tasks call the "network_connections.py"
library for each connection profile individually (using with_items),
pass all profiles at once.

The advantage is:

 - the module can validate the input arguments better as it has
   access to all profiles. For example, when a slave connection
   refers to another master profile from the same play. Previously,
   each invocation of the module only sees the current profile and
   cannot verify whether the reference is valid.

 - while configuring the network, the play might need to shortly
   disconnect the control connection. In the previous way, after
   tearing down the network the target host becomes unreachable for
   ansible and the following steps cannot be executed anymore.
   Now, all steps are done as a whole on the target host, via
   one connection. If the host becomes unreachable for a short
   time, that is not a problem as long as the connectivty is
   restored at the end.
   Ansible also supports to switch the host IP (or SSH port). With
   this new way, the ansible play can apply a bunch of profiles
   autonomously and the ansible play can potentially handle a changing
   IP configuration.
This commit is contained in:
Thomas Haller 2016-12-16 16:56:12 +01:00
parent 407e3b81af
commit edcb4a2850
4 changed files with 1267 additions and 715 deletions

View file

@ -9,6 +9,7 @@
type: ethernet
- name: p-3-auto
- wait: 10
- name: p-3-manual
autoconnect: no
@ -22,6 +23,7 @@
- "192.168.122.{{ network_iphost }}/24"
- name: p-3-manual
- wait: 10
- name: p-3-br0
autoconnect: no

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,250 @@
#!/usr/bin/env python
import sys
import os
import unittest
sys.path.insert(1, os.path.dirname(os.path.abspath(__file__)))
import network_connections as n
class TestValidator(unittest.TestCase):
def test_validate_str(self):
v = n.ArgValidatorStr('state')
self.assertEqual('a', v.validate('a'))
with self.assertRaises(n.ValidationError):
v.validate(1)
with self.assertRaises(n.ValidationError):
v.validate(None)
v = n.ArgValidatorStr('state', required = True)
with self.assertRaises(n.ValidationError):
v.validate(None)
def test_validate_int(self):
v = n.ArgValidatorInt('state', default_value = None)
self.assertEqual(1, v.validate(1))
self.assertEqual(1, v.validate("1"))
with self.assertRaises(n.ValidationError):
v.validate(None)
with self.assertRaises(n.ValidationError):
v.validate("1a")
v = n.ArgValidatorInt('state', required = True)
with self.assertRaises(n.ValidationError):
v.validate(None)
def test_validate_bool(self):
v = n.ArgValidatorBool('state')
self.assertEqual(True, v.validate(True))
self.assertEqual(True, v.validate("True"))
self.assertEqual(True, v.validate(1))
self.assertEqual(False, v.validate(False))
self.assertEqual(False, v.validate("False"))
self.assertEqual(False, v.validate(0))
with self.assertRaises(n.ValidationError):
v.validate(2)
with self.assertRaises(n.ValidationError):
v.validate(None)
v = n.ArgValidatorBool('state', required = True)
with self.assertRaises(n.ValidationError):
v.validate(None)
def test_validate_dict(self):
v = n.ArgValidatorDict(
'dict',
nested = [
n.ArgValidatorInt('i', required = True),
n.ArgValidatorStr('s', required = False, default_value = 's_default'),
n.ArgValidatorStr('l', required = False, default_value = n.ArgValidator.MISSING),
])
self.assertEqual(
{
'i': 5,
's': 's_default',
},
v.validate({
'i': '5',
})
)
self.assertEqual(
{
'i': 5,
's': 's_default',
'l': '6',
},
v.validate({
'i': '5',
'l': '6',
})
)
with self.assertRaises(n.ValidationError):
v.validate({ 'k': 1 })
def test_validate_list(self):
v = n.ArgValidatorList(
'list',
nested = n.ArgValidatorInt('i')
)
self.assertEqual(
[ 1, 5 ],
v.validate([ '1', 5 ])
)
with self.assertRaises(n.ValidationError):
v.validate([1, 's'])
def test_1(self):
self.assertEqual(
[],
n.AnsibleUtil.ARGS_CONNECTIONS.validate([]),
)
self.assertEqual(
[
{
'name': '5',
'state': 'present',
'type': 'ethernet',
'autoconnect': True,
'parent': None,
'ip': {
'gateway6': None,
'gateway4': None,
'route_metric4': None,
'auto6': True,
'dhcp4': True,
'address': [],
'route_metric6': None,
'ip_is_present': False,
'dhcp4_send_hostname': None,
},
'mac': None,
'master': None,
'vlan_id': None,
'on_error':
'fail',
'interface_name': None,
'slave_type': None,
},
{
'name': '5',
'state': 'up',
'wait': 90,
'on_error': 'fail',
}
],
n.AnsibleUtil.ARGS_CONNECTIONS.validate([
{ 'name': '5',
'type': 'ethernet',
},
{ 'name': '5' }
]),
)
self.assertEqual(
[
{
'name': '5',
'state': 'up',
'type': 'ethernet',
'autoconnect': True,
'parent': None,
'ip': {
'gateway6': None,
'gateway4': None,
'route_metric4': None,
'auto6': True,
'dhcp4': True,
'address': [],
'route_metric6': None,
'ip_is_present': False,
'dhcp4_send_hostname': None,
},
'mac': None,
'master': None,
'vlan_id': None,
'on_error':
'fail',
'interface_name': None,
'slave_type': None,
'wait': 90,
},
],
n.AnsibleUtil.ARGS_CONNECTIONS.validate([
{ 'name': '5',
'state': 'up',
'type': 'ethernet',
},
]),
)
with self.assertRaises(n.ValidationError):
n.AnsibleUtil.ARGS_CONNECTIONS.validate([ { 'name': 'a', 'autoconnect': True }])
self.assertEqual(
[
{
'name': '5',
'state': 'absent',
'on_error': 'fail',
}
],
n.AnsibleUtil.ARGS_CONNECTIONS.validate([
{
'name': '5',
'state': 'absent',
}
]),
)
with self.assertRaises(n.ValidationError):
n.AnsibleUtil.ARGS_CONNECTIONS.validate([ { } ])
with self.assertRaises(n.ValidationError):
n.AnsibleUtil.ARGS_CONNECTIONS.validate([ { 'name': 'b', 'xxx': 5 } ])
self.assertEqual(
[
{
'autoconnect': True,
'interface_name': None,
'ip': {
'address': [],
'auto6': True,
'dhcp4': True,
'dhcp4_send_hostname': None,
'gateway4': None,
'gateway6': None,
'ip_is_present': False,
'route_metric4': None,
'route_metric6': None,
},
'mac': None,
'master': None,
'name': '5',
'parent': None,
'on_error': 'fail',
'slave_type': None,
'state': 'present',
'type': 'ethernet',
'vlan_id': None,
},
],
n.AnsibleUtil.ARGS_CONNECTIONS.validate([
{
'name': '5',
'type': 'ethernet',
}
]),
)
if __name__ == '__main__':
unittest.main()

View file

@ -15,19 +15,7 @@
- name: Configure networking connection profiles
network_connections:
provider: "{{ network.provider | default(network_provider) | mandatory }}"
name: "{{ item.name | mandatory }}"
state: "{{ item.state | default(omit) }}"
wait: "{{ item.wait | default(omit) }}"
type: "{{ item.type | default(omit) }}"
autoconnect: "{{ item.autoconnect | default(omit) }}"
slave_type: "{{ item.slave_type | default(omit) }}"
master: "{{ item.master | default(omit) }}"
interface_name: "{{ item.interface_name | default(omit) }}"
mac: "{{ item.mac | default(omit) }}"
parent: "{{ item.parent | default(omit) }}"
vlan_id: "{{ item.vlan_id | default(omit) }}"
ip: "{{ item.ip | default(omit) }}"
with_items: "{{ network.connections | default([]) }}"
connections: "{{ network.connections | default([]) }}"
- name: Re-test connectivity
ping: