From 9071f762fb506b2bdbe8ce8fddb1037d3226046c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= Date: Tue, 13 Aug 2019 12:26:59 +0200 Subject: [PATCH] virt.network_define allow adding IP configuration If using virt.network_define with nat network type, then libvirt complains about missing IP configuration. Allow setting it in both the virt.network_define module and the virt.network_running state. --- salt/modules/virt.py | 47 +++++++++++++++++++++-- salt/states/virt.py | 38 ++++++++++++++++++ salt/templates/virt/libvirt_network.jinja | 13 ++++++- tests/unit/modules/test_virt.py | 26 +++++++++++++ tests/unit/states/test_virt.py | 26 +++++++++++++ 5 files changed, 146 insertions(+), 4 deletions(-) diff --git a/salt/modules/virt.py b/salt/modules/virt.py index 74fa7da0c188..8001ad0c62ec 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -110,6 +110,7 @@ from salt.exceptions import CommandExecutionError, SaltInvocationError from salt.ext import six from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin +from salt._compat import ipaddress log = logging.getLogger(__name__) @@ -654,7 +655,8 @@ def _gen_net_xml(name, bridge, forward, vport, - tag=None): + tag=None, + ip_configs=None): ''' Generate the XML string to define a libvirt network ''' @@ -664,6 +666,10 @@ def _gen_net_xml(name, 'forward': forward, 'vport': vport, 'tag': tag, + 'ip_configs': [{ + 'address': ipaddress.ip_network(config['cidr']), + 'dhcp_ranges': config.get('dhcp_ranges', []), + } for config in ip_configs or []], } fn_ = 'libvirt_network.jinja' try: @@ -4279,7 +4285,12 @@ def cpu_baseline(full=False, migratable=False, out='libvirt', **kwargs): return cpu.toxml() -def network_define(name, bridge, forward, **kwargs): +def network_define(name, + bridge, + forward, + ipv4_config=None, + ipv6_config=None, + **kwargs): ''' Create libvirt network. @@ -4290,10 +4301,38 @@ def network_define(name, bridge, forward, **kwargs): :param tag: Vlan tag :param autostart: Network autostart (default True) :param start: Network start (default True) + :param ipv4_config: IP v4 configuration + Dictionary describing the IP v4 setup like IP range and + a possible DHCP configuration. The structure is documented + in net-define-ip_. + + ..versionadded:: Neon + :type ipv4_config: dict or None + + :param ipv6_config: IP v6 configuration + Dictionary describing the IP v6 setup like IP range and + a possible DHCP configuration. The structure is documented + in net-define-ip_. + + ..versionadded:: Neon + :type ipv6_config: dict or None + :param connection: libvirt connection URI, overriding defaults :param username: username to connect with, overriding defaults :param password: password to connect with, overriding defaults + .. _net-define-ip: + + ** IP configuration definition + + Both the IPv4 and IPv6 configuration dictionaries can contain the following properties: + + cidr + CIDR notation for the network. For example '192.168.124.0/24' + + dhcp_ranges + A list of dictionary with ``'start'`` and ``'end'`` properties. + CLI Example: .. code-block:: bash @@ -4307,12 +4346,14 @@ def network_define(name, bridge, forward, **kwargs): tag = kwargs.get('tag', None) autostart = kwargs.get('autostart', True) starting = kwargs.get('start', True) + net_xml = _gen_net_xml( name, bridge, forward, vport, - tag, + tag=tag, + ip_configs=[config for config in [ipv4_config, ipv6_config] if config], ) try: conn.networkDefineXML(net_xml) diff --git a/salt/states/virt.py b/salt/states/virt.py index fb3980e19610..9d78fe012e30 100644 --- a/salt/states/virt.py +++ b/salt/states/virt.py @@ -637,6 +637,8 @@ def network_running(name, forward, vport=None, tag=None, + ipv4_config=None, + ipv6_config=None, autostart=True, connection=None, username=None, @@ -644,6 +646,25 @@ def network_running(name, ''' Defines and starts a new network with specified arguments. + :param bridge: Bridge name + :param forward: Forward mode(bridge, router, nat) + :param vport: Virtualport type (Default: ``'None'``) + :param tag: Vlan tag (Default: ``'None'``) + :param ipv4_config: + IPv4 network configuration. See the :py:func`virt.network_define + ` function corresponding parameter documentation + for more details on this dictionary. + (Default: None). + + .. versionadded:: neon + :param ipv6_config: + IPv6 network configuration. See the :py:func`virt.network_define + ` function corresponding parameter documentation + for more details on this dictionary. + (Default: None). + + .. versionadded:: neon + :param autostart: Network autostart (default ``'True'``) :param connection: libvirt connection URI, overriding defaults .. versionadded:: 2019.2.0 @@ -669,6 +690,21 @@ def network_running(name, - tag: 180 - autostart: True + .. code-block:: yaml + + network_name: + virt.network_define: + - bridge: natted + - forward: nat + - ipv4_config: + cidr: 192.168.42.0/24 + dhcp_ranges: + - start: 192.168.42.10 + end: 192.168.42.25 + - start: 192.168.42.100 + end: 192.168.42.150 + - autostart: True + ''' ret = {'name': name, 'changes': {}, @@ -691,6 +727,8 @@ def network_running(name, forward, vport=vport, tag=tag, + ipv4_config=ipv4_config, + ipv6_config=ipv6_config, autostart=autostart, start=True, connection=connection, diff --git a/salt/templates/virt/libvirt_network.jinja b/salt/templates/virt/libvirt_network.jinja index d0db99cad858..2f11e6455923 100644 --- a/salt/templates/virt/libvirt_network.jinja +++ b/salt/templates/virt/libvirt_network.jinja @@ -6,4 +6,15 @@ {% endif %} - \ No newline at end of file + {% for ip_config in ip_configs %} + + + {% for range in ip_config.dhcp_ranges %} + + {% endfor %} + + + {% endfor %} + diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py index 7a9b549b6463..b4bb1175d628 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py @@ -1939,6 +1939,32 @@ def test_network(self): self.assertEqual(root.find('forward').attrib['mode'], 'bridge') self.assertEqual(root.find('virtualport').attrib['type'], 'openvswitch') + def test_network_nat(self): + ''' + Test virt._get_net_xml() in a nat setup + ''' + xml_data = virt._gen_net_xml('network', 'main', 'nat', None, ip_configs=[ + { + 'cidr': '192.168.2.0/24', + 'dhcp_ranges': [ + {'start': '192.168.2.10', 'end': '192.168.2.25'}, + {'start': '192.168.2.110', 'end': '192.168.2.125'}, + ] + } + ]) + root = ET.fromstring(xml_data) + self.assertEqual(root.find('name').text, 'network') + self.assertEqual(root.find('bridge').attrib['name'], 'main') + self.assertEqual(root.find('forward').attrib['mode'], 'nat') + self.assertEqual(root.find("./ip[@address='192.168.2.0']").attrib['prefix'], '24') + self.assertEqual(root.find("./ip[@address='192.168.2.0']").attrib['family'], 'ipv4') + self.assertEqual( + root.find("./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.10']").attrib['end'], + '192.168.2.25') + self.assertEqual( + root.find("./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.110']").attrib['end'], + '192.168.2.125') + def test_domain_capabilities(self): ''' Test the virt.domain_capabilities parsing diff --git a/tests/unit/states/test_virt.py b/tests/unit/states/test_virt.py index 8712e2e204df..9467cd3502d1 100644 --- a/tests/unit/states/test_virt.py +++ b/tests/unit/states/test_virt.py @@ -599,6 +599,19 @@ def test_network_running(self): 'bridge', vport='openvswitch', tag=180, + ipv4_config={ + 'cidr': '192.168.2.0/24', + 'dhcp_ranges': [ + {'start': '192.168.2.10', 'end': '192.168.2.25'}, + {'start': '192.168.2.110', 'end': '192.168.2.125'}, + ] + }, + ipv6_config={ + 'cidr': '2001:db8:ca2:2::1/64', + 'dhcp_ranges': [ + {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, + ] + }, autostart=False, connection='myconnection', username='user', @@ -610,6 +623,19 @@ def test_network_running(self): tag=180, autostart=False, start=True, + ipv4_config={ + 'cidr': '192.168.2.0/24', + 'dhcp_ranges': [ + {'start': '192.168.2.10', 'end': '192.168.2.25'}, + {'start': '192.168.2.110', 'end': '192.168.2.125'}, + ] + }, + ipv6_config={ + 'cidr': '2001:db8:ca2:2::1/64', + 'dhcp_ranges': [ + {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, + ] + }, connection='myconnection', username='user', password='secret')