diff --git a/salt/modules/virt.py b/salt/modules/virt.py index 6588532d47b0..a5c4c95d09e7 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -112,6 +112,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__) @@ -663,7 +664,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 ''' @@ -673,6 +675,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: @@ -4402,7 +4408,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. @@ -4413,10 +4424,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 @@ -4430,12 +4469,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 53e67ea7e0ba..f0b9290ea7fe 100644 --- a/salt/states/virt.py +++ b/salt/states/virt.py @@ -658,6 +658,8 @@ def network_running(name, forward, vport=None, tag=None, + ipv4_config=None, + ipv6_config=None, autostart=True, connection=None, username=None, @@ -665,6 +667,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 @@ -690,6 +711,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': {}, @@ -712,6 +748,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 9194fc0ebac9..6f472c656cd6 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py @@ -2016,6 +2016,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 478d061fcfa9..c50c04b8abea 100644 --- a/tests/unit/states/test_virt.py +++ b/tests/unit/states/test_virt.py @@ -616,6 +616,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', @@ -627,6 +640,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')