salt/states/network.py
# -*- coding: utf-8 -*-
'''
Configuration of network interfaces
===================================
The network module is used to create and manage network settings,
interfaces can be set as either managed or ignored. By default
all interfaces are ignored unless specified.
.. note::
RedHat-based systems (RHEL, CentOS, Scientific, etc.)
have been supported since version 2014.1.0.
Debian-based systems (Debian, Ubuntu, etc.) have been
supported since version 2017.7.0. The following options
are not supported: ipaddr_start, and ipaddr_end.
Other platforms are not yet supported.
.. note::
On Debian-based systems, networking configuration can be specified
in `/etc/network/interfaces` or via included files such as (by default)
`/etc/network/interfaces.d/*`. This can be problematic for configuration
management. It is recommended to use either `file.managed` *or*
`network.managed`.
If using `network.managed`, it can be useful to ensure `interfaces.d/`
is empty. This can be done using:
/etc/network/interfaces.d:
file.directory:
- clean: True
.. code-block:: yaml
system:
network.system:
- enabled: True
- hostname: server1.example.com
- gateway: 192.168.0.1
- gatewaydev: eth0
- nozeroconf: True
- nisdomain: example.com
- require_reboot: True
eth0:
network.managed:
- enabled: True
- type: eth
- proto: static
- ipaddr: 10.1.0.7
- netmask: 255.255.255.0
- gateway: 10.1.0.1
- enable_ipv6: true
- ipv6proto: static
- ipv6ipaddrs:
- 2001:db8:dead:beef::3/64
- 2001:db8:dead:beef::7/64
- ipv6gateway: 2001:db8:dead:beef::1
- ipv6netmask: 64
- dns:
- 8.8.8.8
- 8.8.4.4
eth0-range0:
network.managed:
- type: eth
- ipaddr_start: 192.168.1.1
- ipaddr_end: 192.168.1.10
- clonenum_start: 10
- mtu: 9000
bond0-range0:
network.managed:
- type: eth
- ipaddr_start: 192.168.1.1
- ipaddr_end: 192.168.1.10
- clonenum_start: 10
- mtu: 9000
eth1.0-range0:
network.managed:
- type: eth
- ipaddr_start: 192.168.1.1
- ipaddr_end: 192.168.1.10
- clonenum_start: 10
- vlan: True
- mtu: 9000
bond0.1-range0:
network.managed:
- type: eth
- ipaddr_start: 192.168.1.1
- ipaddr_end: 192.168.1.10
- clonenum_start: 10
- vlan: True
- mtu: 9000
.. note::
add support of ranged interfaces (vlan, bond and eth) for redhat system,
Important:type must be eth.
routes:
network.routes:
- name: eth0
- routes:
- name: secure_network
ipaddr: 10.2.0.0
netmask: 255.255.255.0
gateway: 10.1.0.3
- name: HQ_network
ipaddr: 10.100.0.0
netmask: 255.255.0.0
gateway: 10.1.0.10
eth2:
network.managed:
- enabled: True
- type: slave
- master: bond0
eth3:
network.managed:
- enabled: True
- type: slave
- master: bond0
eth4:
network.managed:
- enabled: True
- type: eth
- proto: dhcp
- bridge: br0
eth5:
network.managed:
- enabled: True
- type: eth
- proto: dhcp
- noifupdown: True # Do not restart the interface
# you need to reboot/reconfigure manualy
bond0:
network.managed:
- type: bond
- ipaddr: 10.1.0.1
- netmask: 255.255.255.0
- mode: gre
- proto: static
- dns:
- 8.8.8.8
- 8.8.4.4
- enabled: False
- slaves: eth2 eth3
- require:
- network: eth2
- network: eth3
- miimon: 100
- arp_interval: 250
- downdelay: 200
- lacp_rate: fast
- max_bonds: 1
- updelay: 0
- use_carrier: on
- hashing-algorithm: layer2
- mtu: 9000
- autoneg: on
- speed: 1000
- duplex: full
- rx: on
- tx: off
- sg: on
- tso: off
- ufo: off
- gso: off
- gro: off
- lro: off
bond0.2:
network.managed:
- type: vlan
- ipaddr: 10.1.0.2
- use:
- network: bond0
- require:
- network: bond0
bond0.3:
network.managed:
- type: vlan
- ipaddr: 10.1.0.3
- use:
- network: bond0
- require:
- network: bond0
bond0.10:
network.managed:
- type: vlan
- ipaddr: 10.1.0.4
- use:
- network: bond0
- require:
- network: bond0
bond0.12:
network.managed:
- type: vlan
- ipaddr: 10.1.0.5
- use:
- network: bond0
- require:
- network: bond0
br0:
network.managed:
- enabled: True
- type: bridge
- proto: dhcp
- bridge: br0
- delay: 0
- ports: eth4
- bypassfirewall: True
- use:
- network: eth4
- require:
- network: eth4
eth6:
network.managed:
- type: eth
- noifupdown: True
# IPv4
- proto: static
- ipaddr: 192.168.4.9
- netmask: 255.255.255.0
- gateway: 192.168.4.1
- enable_ipv6: True
# IPv6
- ipv6proto: static
- ipv6ipaddr: 2001:db8:dead:c0::3
- ipv6netmask: 64
- ipv6gateway: 2001:db8:dead:c0::1
# override shared; makes those options v4-only
- ipv6ttl: 15
# Shared
- mtu: 1480
- ttl: 18
- dns:
- 8.8.8.8
- 8.8.4.4
eth7:
- type: eth
- proto: static
- ipaddr: 10.1.0.7
- netmask: 255.255.255.0
- gateway: 10.1.0.1
- enable_ipv6: True
- ipv6proto: static
- ipv6ipaddr: 2001:db8:dead:beef::3
- ipv6netmask: 64
- ipv6gateway: 2001:db8:dead:beef::1
- noifupdown: True
eth8:
network.managed:
- enabled: True
- type: eth
- proto: static
- enable_ipv6: true
- ipv6proto: static
- ipv6ipaddrs:
- 2001:db8:dead:beef::3/64
- 2001:db8:dead:beef::7/64
- ipv6gateway: 2001:db8:dead:beef::1
- ipv6netmask: 64
- dns:
- 8.8.8.8
- 8.8.4.4
system:
network.system:
- enabled: True
- hostname: server1.example.com
- gateway: 192.168.0.1
- gatewaydev: eth0
- nozeroconf: True
- nisdomain: example.com
- require_reboot: True
- apply_hostname: True
lo:
network.managed:
- name: lo
- type: eth
- proto: loopback
- onboot: yes
- userctl: no
- ipv6_autoconf: no
- enable_ipv6: true
.. note::
Apply changes to hostname immediately.
.. versionadded:: 2015.5.0
system:
network.system:
- hostname: server2.example.com
- apply_hostname: True
- retain_settings: True
.. note::
Use `retain_settings` to retain current network settings that are not
otherwise specified in the state. Particularly useful if only setting
the hostname. Default behavior is to delete unspecified network
settings.
.. versionadded:: 2016.11.0
.. note::
When managing bridged interfaces on a Debian or Ubuntu based system, the
ports argument is required. Red Hat systems will ignore the argument.
'''
from __future__ import absolute_import, unicode_literals, print_function
# Import Python libs
import difflib
# Import Salt libs
import salt.utils.network
import salt.utils.platform
import salt.loader
# Import 3rd party libs
from salt.ext import six
# Set up logging
import logging
log = logging.getLogger(__name__)
def __virtual__():
'''
Confine this module to non-Windows systems with the required execution
module available.
'''
if not salt.utils.platform.is_windows() and 'ip.get_interface' in __salt__:
return True
return False
def managed(name, type, enabled=True, **kwargs):
'''
Ensure that the named interface is configured properly.
name
The name of the interface to manage
type
Type of interface and configuration.
enabled
Designates the state of this interface.
kwargs
The IP parameters for this interface.
'''
# For this function we are purposefully overwriting a bif
# to enhance the user experience. This does not look like
# it will cause a problem. Just giving a heads up in case
# it does create a problem.
ret = {
'name': name,
'changes': {},
'result': True,
'comment': 'Interface {0} is up to date.'.format(name),
}
if 'test' not in kwargs:
kwargs['test'] = __opts__.get('test', False)
# set ranged status
apply_ranged_setting = False
# Build interface
try:
old = __salt__['ip.get_interface'](name)
new = __salt__['ip.build_interface'](name, type, enabled, **kwargs)
if kwargs['test']:
if old == new:
pass
if not old and new:
ret['result'] = None
ret['comment'] = 'Interface {0} is set to be ' \
'added.'.format(name)
elif old != new:
diff = difflib.unified_diff(old, new, lineterm='')
ret['result'] = None
ret['comment'] = 'Interface {0} is set to be ' \
'updated:\n{1}'.format(name, '\n'.join(diff))
else:
if not old and new:
ret['comment'] = 'Interface {0} ' \
'added.'.format(name)
ret['changes']['interface'] = 'Added network interface.'
apply_ranged_setting = True
elif old != new:
diff = difflib.unified_diff(old, new, lineterm='')
ret['comment'] = 'Interface {0} ' \
'updated.'.format(name)
ret['changes']['interface'] = '\n'.join(diff)
apply_ranged_setting = True
except AttributeError as error:
ret['result'] = False
ret['comment'] = six.text_type(error)
return ret
# Debian based system can have a type of source
# in the interfaces file, we don't ifup or ifdown it
if type == 'source':
return ret
# Setup up bond modprobe script if required
if type == 'bond':
try:
old = __salt__['ip.get_bond'](name)
new = __salt__['ip.build_bond'](name, **kwargs)
if kwargs['test']:
if not old and new:
ret['result'] = None
ret['comment'] = 'Bond interface {0} is set to be ' \
'added.'.format(name)
elif old != new:
diff = difflib.unified_diff(old, new, lineterm='')
ret['result'] = None
ret['comment'] = 'Bond interface {0} is set to be ' \
'updated:\n{1}'.format(name, '\n'.join(diff))
else:
if not old and new:
ret['comment'] = 'Bond interface {0} ' \
'added.'.format(name)
ret['changes']['bond'] = 'Added bond {0}.'.format(name)
apply_ranged_setting = True
elif old != new:
diff = difflib.unified_diff(old, new, lineterm='')
ret['comment'] = 'Bond interface {0} ' \
'updated.'.format(name)
ret['changes']['bond'] = '\n'.join(diff)
apply_ranged_setting = True
except AttributeError as error:
#TODO Add a way of reversing the interface changes.
ret['result'] = False
ret['comment'] = six.text_type(error)
return ret
if kwargs['test']:
return ret
# For Redhat/Centos ranged network
if "range" in name:
if apply_ranged_setting:
try:
ret['result'] = __salt__['service.restart']('network')
ret['comment'] = "network restarted for change of ranged interfaces"
return ret
except Exception as error:
ret['result'] = False
ret['comment'] = six.text_type(error)
return ret
ret['result'] = True
ret['comment'] = "no change, passing it"
return ret
# Bring up/shutdown interface
try:
# Get Interface current status
interfaces = salt.utils.network.interfaces()
interface_status = False
if name in interfaces:
interface_status = interfaces[name].get('up')
else:
for iface in interfaces:
if 'secondary' in interfaces[iface]:
for second in interfaces[iface]['secondary']:
if second.get('label', '') == name:
interface_status = True
if enabled:
if 'noifupdown' not in kwargs:
if interface_status:
if ret['changes']:
# Interface should restart to validate if it's up
__salt__['ip.down'](name, type)
__salt__['ip.up'](name, type)
ret['changes']['status'] = 'Interface {0} restart to validate'.format(name)
else:
__salt__['ip.up'](name, type)
ret['changes']['status'] = 'Interface {0} is up'.format(name)
else:
if 'noifupdown' not in kwargs:
if interface_status:
__salt__['ip.down'](name, type)
ret['changes']['status'] = 'Interface {0} down'.format(name)
except Exception as error:
ret['result'] = False
ret['comment'] = six.text_type(error)
return ret
# Try to enslave bonding interfaces after master was created
if type == 'bond' and 'noifupdown' not in kwargs:
if 'slaves' in kwargs and kwargs['slaves']:
# Check that there are new slaves for this master
present_slaves = __salt__['cmd.run'](
['cat', '/sys/class/net/{0}/bonding/slaves'.format(name)]).split()
desired_slaves = kwargs['slaves'].split()
missing_slaves = set(desired_slaves) - set(present_slaves)
# Enslave only slaves missing in master
if missing_slaves:
ifenslave_path = __salt__['cmd.run'](['which', 'ifenslave']).strip()
if ifenslave_path:
log.info("Adding slaves '%s' to the master %s",
' '.join(missing_slaves), name)
cmd = [ifenslave_path, name] + list(missing_slaves)
__salt__['cmd.run'](cmd, python_shell=False)
else:
log.error("Command 'ifenslave' not found")
ret['changes']['enslave'] = (
"Added slaves '{0}' to master '{1}'"
.format(' '.join(missing_slaves), name))
else:
log.info("All slaves '%s' are already added to the master %s"
", no actions required",
' '.join(missing_slaves), name)
if enabled and interface_status:
# Interface was restarted, return
return ret
# TODO: create saltutil.refresh_grains that fires events to the minion daemon
grains_info = salt.loader.grains(__opts__, True)
__grains__.update(grains_info)
__salt__['saltutil.refresh_modules']()
return ret
def routes(name, **kwargs):
'''
Manage network interface static routes.
name
Interface name to apply the route to.
kwargs
Named routes
'''
ret = {
'name': name,
'changes': {},
'result': True,
'comment': 'Interface {0} routes are up to date.'.format(name),
}
apply_routes = False
if 'test' not in kwargs:
kwargs['test'] = __opts__.get('test', False)
# Build interface routes
try:
old = __salt__['ip.get_routes'](name)
new = __salt__['ip.build_routes'](name, **kwargs)
if kwargs['test']:
if old == new:
return ret
if not old and new:
ret['result'] = None
ret['comment'] = 'Interface {0} routes are set to be added.'.format(name)
return ret
elif old != new:
diff = difflib.unified_diff(old, new, lineterm='')
ret['result'] = None
ret['comment'] = 'Interface {0} routes are set to be ' \
'updated:\n{1}'.format(name, '\n'.join(diff))
return ret
if not old and new:
apply_routes = True
ret['comment'] = 'Interface {0} routes added.'.format(name)
ret['changes']['network_routes'] = 'Added interface {0} routes.'.format(name)
elif old != new:
diff = difflib.unified_diff(old, new, lineterm='')
apply_routes = True
ret['comment'] = 'Interface {0} routes updated.'.format(name)
ret['changes']['network_routes'] = '\n'.join(diff)
except AttributeError as error:
ret['result'] = False
ret['comment'] = six.text_type(error)
return ret
# Apply interface routes
if apply_routes:
try:
__salt__['ip.apply_network_settings'](**kwargs)
except AttributeError as error:
ret['result'] = False
ret['comment'] = six.text_type(error)
return ret
return ret
def system(name, **kwargs):
'''
Ensure that global network settings are configured properly.
name
Custom name to represent this configuration change.
kwargs
The global parameters for the system.
'''
ret = {
'name': name,
'changes': {},
'result': True,
'comment': 'Global network settings are up to date.',
}
apply_net_settings = False
kwargs['test'] = __opts__['test']
# Build global network settings
try:
old = __salt__['ip.get_network_settings']()
new = __salt__['ip.build_network_settings'](**kwargs)
if __opts__['test']:
if old == new:
return ret
if not old and new:
ret['result'] = None
ret['comment'] = 'Global network settings are set to be added.'
return ret
elif old != new:
diff = difflib.unified_diff(old, new, lineterm='')
ret['result'] = None
ret['comment'] = 'Global network settings are set to be ' \
'updated:\n{0}'.format('\n'.join(diff))
return ret
if not old and new:
apply_net_settings = True
ret['changes']['network_settings'] = 'Added global network settings.'
elif old != new:
diff = difflib.unified_diff(old, new, lineterm='')
apply_net_settings = True
ret['changes']['network_settings'] = '\n'.join(diff)
except AttributeError as error:
ret['result'] = False
ret['comment'] = six.text_type(error)
return ret
except KeyError as error:
ret['result'] = False
ret['comment'] = six.text_type(error)
return ret
# Apply global network settings
if apply_net_settings:
try:
__salt__['ip.apply_network_settings'](**kwargs)
except AttributeError as error:
ret['result'] = False
ret['comment'] = six.text_type(error)
return ret
return ret