Add network module for NILinuxRT: nilrt_ip.py

Connman is the network manager used as backend.
Functionalities:
  - ip.get_interface_details()
  - ip.up(interface)
  - ip.down(interface)
  - ip.set_dhcp_linklocal_all(interface)
  - ip.set_static_all(interface, address, netmask, gateway, domains)
  - ip.build_interface(interface, interface_type, enable, **settings)
  - ip.build_network_settings(**settings)
  - ip.get_network_settings()
  - ip.apply_network_settings(**settings)

Integration test for:
  - ip.enable
  - ip.disable
  - ip.set_dhcp_linklocal_all
  - ip.set_static_all

Signed-off-by: Heghedus Razvan <razvan.heghedus@ni.com>
This commit is contained in:
Heghedus Razvan 2016-09-07 12:35:57 +03:00 committed by Heghedus Razvan
parent 11a99d8be0
commit 8408789511
2 changed files with 635 additions and 0 deletions

517
salt/modules/nilrt_ip.py Normal file
View File

@ -0,0 +1,517 @@
# -*- coding: utf-8 -*-
'''
The networking module for NI Linux Real-Time distro
'''
# Import python libs
from __future__ import absolute_import
import logging
import time
log = logging.getLogger(__name__)
# Import salt libs
import salt.utils
import salt.utils.validate.net
import salt.exceptions
# Import third party libs
import pyconnman
import dbus
# Define the module's virtual name
__virtualname__ = 'ip'
SERVICE_PATH = '/net/connman/service/'
_CONFIG_TRUE = ['yes', 'on', 'true', '1', True]
def __virtual__():
'''
Confine this module to NI Linux Real-Time based distros
'''
if __grains__['os_family'] == 'NILinuxRT':
try:
state = _get_state
if state == 'offline':
return False, 'Connmand is not running'
except Exception as exc:
return False, str(exc)
return __virtualname__
return False, 'The nilrt_ip module could not be loaded: unsupported OS family'
def _get_state():
try:
state = pyconnman.ConnManager().get_property('State')
except Exception as exc:
raise salt.exceptions.CommandExecutionError('Connman daemon error: {0}'.format(exc))
return state
def _get_technologies():
strTech = ''
technologies = pyconnman.ConnManager().get_technologies()
for path, params in technologies:
strTech += '{0}\n\tName = {1}\n\tType = {2}\n\tPowered = {3}\n\tConnected = {4}\n'.format(
path, params['Name'], params['Type'], params['Powered'] == 1, params['Connected'] == 1)
return strTech
def _add_path(service):
return '{0}{1}'.format(SERVICE_PATH, service)
def _get_services():
'''
Returns a list with all connman services.
'''
serviceList = []
services = pyconnman.ConnManager().get_services()
for path, params in services:
serviceList.append(str(path[len(SERVICE_PATH):]))
return serviceList
def _connected(service):
'''
Verify if a connman service is connected
'''
state = pyconnman.ConnService(_add_path(service)).get_property('State')
return state == 'online' or state == 'ready'
def _space_delimited_list(value):
'''
validate that a value contains one or more space-delimited values
'''
valid, _value, errmsg = False, value, 'space-delimited string'
try:
if hasattr(value, '__iter__'):
valid = True
else:
_value = value.split()
if _value == []:
raise ValueError
valid = True
except AttributeError:
errmsg = '{0} is not a valid list.\n'.format(value)
except ValueError:
errmsg = '{0} is not a valid list.\n'.format(value)
return (valid, errmsg)
def _validate_ipv4(value):
'''
validate ipv4 values
'''
if len(value) == 3:
if not salt.utils.validate.net.ipv4_addr(value[0].strip()):
return (False, 'Invalid ip address: {0} for ipv4 option'.format(value[0]))
if not salt.utils.validate.net.netmask(value[1].strip()):
return (False, 'Invalid netmask: {0} for ipv4 option'.format(value[1]))
if not salt.utils.validate.net.ipv4_addr(value[2].strip()):
return (False, 'Invalid gateway: {0} for ipv4 option'.format(value[2]))
else:
return (False, 'Invalid value: {0} for ipv4 option'.format(value))
return (True, '')
def _interface_to_service(iface):
'''
returns the coresponding service to given interface if exists, otherwise return None
'''
for _service in _get_services():
service_info = pyconnman.ConnService(_add_path(_service))
if service_info.get_property('Ethernet')['Interface'] == iface:
return _service
return None
def _get_service_info(service):
'''
return details about given connman service
'''
service_info = pyconnman.ConnService(_add_path(service))
data = {
'name': service,
'wireless': service_info.get_property('Type') == 'wifi',
'connectionid': str(service_info.get_property('Ethernet')['Interface']),
'HWAddress': str(service_info.get_property('Ethernet')['Address'])
}
state = service_info.get_property('State')
if state == 'ready' or state == 'online':
data['up'] = True
data['ipv4'] = {}
ipv4 = 'IPv4'
if service_info.get_property('IPv4')['Method'] == 'manual':
ipv4 += '.Configuration'
ipv4Info = service_info.get_property(ipv4)
for info in ['Method', 'Address', 'Netmask', 'Gateway']:
try:
value = ipv4Info[info]
if info == 'Method':
info = 'requestmode'
if value == 'dhcp':
value = 'dhcp_linklocal'
elif value == 'manual':
value = 'static'
data['ipv4'][info.lower()] = str(value)
except Exception as exc:
log.warning('Unable to get IPv4 {0} for service {1}\n'.format(info, service))
ipv6Info = service_info.get_property('IPv6')
for info in ['Address', 'Prefix', 'Gateway']:
try:
value = ipv6Info[info]
data['ipv6'][info.lower()] = [str(value)]
except Exception as exc:
log.warning('Unable to get IPv6 {0} for service {1}\n'.format(info, service))
domains = []
for x in service_info.get_property('Domains'):
domains.append(str(x))
data['ipv4']['dns'] = domains
else:
data['up'] = False
if 'ipv4' in data:
data['ipv4']['supportedrequestmodes'] = [
'static',
'dhcp_linklocal'
]
return data
def _dict_to_string(dictionary):
'''
converts a dictionary object into a list of strings
'''
ret = ''
for key, val in sorted(dictionary.items()):
if isinstance(val, dict):
for line in _dict_to_string(val):
ret += str(key) + '-' + line + '\n'
elif isinstance(val, list):
stringList = ''
for item in val:
stringList += str(item) + ' '
ret += str(key) + ': ' + stringList +'\n'
else:
ret += str(key) + ': ' + str(val) +'\n'
return ret.splitlines()
def get_interfaces_details():
'''
Get details about all the interfaces on the minion
:return: information about all connmans interfaces
:rtype: dictionary
CLI Example:
.. code-block:: bash
salt '*' ip.get_interfaces_details
'''
services = []
for service in _get_services():
services.append(_get_service_info(service))
interfaceList = {'interfaces': services}
return interfaceList
def up(interface, iface_type=None):
'''
Enable the specified interface
:param str interface: interface label
:return: True if the service was enabled, otherwise an exception will be thrown.
:rtype: bool
CLI Example:
.. code-block:: bash
salt '*' ip.up interface-label
'''
service = _interface_to_service(interface)
if not service:
raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
if not _connected(service):
service = pyconnman.ConnService(_add_path(service))
try:
state = service.connect()
return state is None
except Exception as exc:
raise salt.exceptions.CommandExecutionError('Couldn\'t enable service: {0}\n'.format(service))
return True
def enable(interface):
'''
Enable the specified interface
:param str interface: interface label
:return: True if the service was enabled, otherwise an exception will be thrown.
:rtype: bool
CLI Example:
.. code-block:: bash
salt '*' ip.enable interface-label
'''
return up(interface)
def down(interface, iface_type=None):
'''
Disable the specified interface
:param str interface: interface label
:return: True if the service was disabled, otherwise an exception will be thrown.
:rtype: bool
CLI Example:
.. code-block:: bash
salt '*' ip.down interface-label
'''
service = _interface_to_service(interface)
if not service:
raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
if _connected(service):
service = pyconnman.ConnService(_add_path(service))
try:
state = service.disconnect()
return state is None
except Exception as exc:
raise salt.exceptions.CommandExecutionError('Couldn\'t disable service: {0}\n'.format(service))
return True
def disable(interface):
'''
Disable the specified interface
:param str interface: interface label
:return: True if the service was disabled, otherwise an exception will be thrown.
:rtype: bool
CLI Example:
.. code-block:: bash
salt '*' ip.disable interface-label
'''
return down(interface)
def set_dhcp_linklocal_all(interface):
'''
Configure specified adapter to use DHCP with linklocal fallback
:param str interface: interface label
:return: True if the settings ware applied, otherwise an exception will be thrown.
:rtype: bool
CLI Example:
.. code-block:: bash
salt '*' ip.dhcp_linklocal_all interface-label
'''
service = _interface_to_service(interface)
if not service:
raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
service = pyconnman.ConnService(_add_path(service))
ipv4 = service.get_property('IPv4.Configuration')
ipv4['Method'] = dbus.String('dhcp', variant_level=1)
ipv4['Address'] = dbus.String('', variant_level=1)
ipv4['Netmask'] = dbus.String('', variant_level=1)
ipv4['Gateway'] = dbus.String('', variant_level=1)
try:
service.set_property('IPv4.Configuration', ipv4)
service.set_property('Domains.Configuration', ['']) # reset domains list
except Exception as exc:
raise salt.exceptions.CommandExecutionError('Couldn\'t set dhcp linklocal for service: {0}\nError: {1}\n'.format(service, exc))
return True
def set_static_all(interface, address, netmask, gateway, domains):
'''
Configure specified adapter to use ipv4 manual settings
:param str interface: interface label
:param str address: ipv4 address
:param str netmask: ipv4 netmask
:param str gateway: ipv4 gateway
:param str domains: list of domains servers separated by spaces
:return: True if the settings were applied, otherwise an exception will be thrown.
:rtype: bool
CLI Example:
.. code-block:: bash
salt '*' ip.dhcp_linklocal_all interface-label address netmask gateway domains
'''
service = _interface_to_service(interface)
if not service:
raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
validate, msg = _validate_ipv4([address, netmask, gateway])
if not validate:
raise salt.exceptions.CommandExecutionError(msg)
validate, msg = _space_delimited_list(domains)
if not validate:
raise salt.exceptions.CommandExecutionError(msg)
service = pyconnman.ConnService(_add_path(service))
ipv4 = service.get_property('IPv4.Configuration')
ipv4['Method'] = dbus.String('manual', variant_level=1)
ipv4['Address'] = dbus.String('{0}'.format(address), variant_level=1)
ipv4['Netmask'] = dbus.String('{0}'.format(netmask), variant_level=1)
ipv4['Gateway'] = dbus.String('{0}'.format(gateway), variant_level=1)
try:
service.set_property('IPv4.Configuration', ipv4)
if not isinstance(domains, list):
dns = domains.split(' ')
domains = dns
service.set_property('Domains.Configuration', [dbus.String('{0}'.format(d)) for d in domains])
except Exception as exc:
raise salt.exceptions.CommandExecutionError('Couldn\'t set manual settings for service: {0}\nError: {1}\n'.format(service, exc))
return True
def get_interface(iface):
'''
Returns details about given interface.
CLI Example:
.. code-block:: bash
salt '*' ip.get_interface eth0
'''
_interfaces = get_interfaces_details()
for _interface in _interfaces['interfaces']:
if _interface['connectionid'] == iface:
return _dict_to_string(_interface)
return None
def build_interface(iface, iface_type, enable, **settings):
'''
Build an interface script for a network interface.
CLI Example:
.. code-block:: bash
salt '*' ip.build_interface eth0 eth <settings>
'''
if iface_type != 'eth':
raise salt.exceptions.CommandExecutionError('Interface type not supported: {0}:'.format(iface_type))
if 'proto' not in settings or settings['proto'] == 'dhcp': # default protocol type used is dhcp
set_dhcp_linklocal_all(iface)
elif settings['proto'] != 'static':
raise salt.exceptions.CommandExecutionError('Protocol type: {0} is not supported'.format(settings['proto']))
else:
address = settings['ipaddr']
netmask = settings['netmask']
gateway = settings['gateway']
dns = []
for key, val in settings.iteritems():
if 'dns' in key or 'domain' in key:
dns += val
if enable:
up(iface)
return get_interface(iface)
def build_network_settings(**settings):
'''
Build the global network script.
CLI Example:
.. code-block:: bash
salt '*' ip.build_network_settings <settings>
'''
changes = []
if 'networking' in settings:
if settings['networking'] in _CONFIG_TRUE:
__salt__['service.enable']('connman')
else:
__salt__['service.disable']('connman')
if 'hostname' in settings:
new_hostname = settings['hostname'].split('.', 1)[0]
settings['hostname'] = new_hostname
old_hostname = __salt__['network.get_hostname']
if new_hostname != old_hostname:
__salt__['network.mod_hostname'](new_hostname)
changes.append('hostname={0}'.format(new_hostname))
return changes
def get_network_settings():
'''
Return the contents of the global network script.
CLI Example:
.. code-block:: bash
salt '*' ip.get_network_settings
'''
settings = []
networking = 'no' if _get_state() == 'offline' else "yes"
settings.append('networking={0}'.format(networking))
hostname = __salt__['network.get_hostname']
settings.append('hostname={0}'.format(hostname))
return settings
def apply_network_settings(**settings):
'''
Apply global network configuration.
CLI Example:
.. code-block:: bash
salt '*' ip.apply_network_settings
'''
if 'require_reboot' not in settings:
settings['require_reboot'] = False
if 'apply_hostname' not in settings:
settings['apply_hostname'] = False
hostname_res = True
if settings['apply_hostname'] in _CONFIG_TRUE:
if 'hostname' in settings:
hostname_res = __salt__['network.mod_hostname'](settings['hostname'])
else:
log.warning(
'The network state sls is trying to apply hostname '
'changes but no hostname is defined.'
)
hostname_res = False
res = True
if settings['require_reboot'] in _CONFIG_TRUE:
log.warning(
'The network state sls is requiring a reboot of the system to '
'properly apply network configuration.'
)
res = True
else:
stop = __salt__['service.stop']('connman')
time.sleep(2)
res = stop and __salt__['service.start']('connman')
return hostname_res and res

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
'''
integration tests for nilirt_ip
'''
# Import python libs
from __future__ import absolute_import
import time
# Import Salt Testing libs
from salttesting import skipIf
from salttesting.helpers import ensure_in_syspath, destructiveTest
ensure_in_syspath('../../')
# Import salt libs
import integration
import salt.utils
@skipIf(not salt.utils.is_linux(), 'These tests can only be run on linux')
class Nilrt_ipModuleTest(integration.ModuleCase):
'''
Validate the nilrt_ip module
'''
def __init__(self, arg):
super(self.__class__, self).__init__(arg)
self.initialState = {}
def setUp(self):
'''
Get current settings
'''
# save files from var/lib/connman*
os_grain = self.run_function('grains.item', ['os_family'])
if os_grain['os_family'] != 'NILinuxRT':
self.skipTest('Tests applicable only to NILinuxRT')
super(Nilrt_ipModuleTest, self).setUp()
if salt.utils.get_uid(salt.utils.get_user()) != 0:
self.skipTest('Test requires root')
self.run_function('file.copy', ['/var/lib/connman', '/tmp/connman', 'recurse=True', 'remove_existing=True'])
def tearDown(self):
'''
Reset to original settings
'''
# restore files
self.run_function('file.copy', ['/tmp/connman', '/var/lib/connman', 'recurse=True', 'remove_existing=True'])
# restart connman
self.run_function('service.restart', ['connman'])
time.sleep(10) # wait 10 seconds for connman to be fully loaded
interfaces = self.__interfaces()
for interface in interfaces:
self.run_function('ip.up', [interface])
def __connected(self, interface):
return interface['up']
def __interfaces(self):
interfaceList = []
for iface in self.run_function('ip.get_interfaces_details')['interfaces']:
interfaceList.append(iface['connectionid'])
return interfaceList
@destructiveTest
def test_down(self):
interfaces = self.__interfaces()
for interface in interfaces:
result = self.run_function('ip.down', [interface])
self.assertTrue(result)
info = self.run_function('ip.get_interfaces_details')
for interface in info['interfaces']:
self.assertFalse(self.__connected(interface))
@destructiveTest
def test_up(self):
interfaces = self.__interfaces()
#first down all interfaces
for interface in interfaces:
self.run_function('ip.down', [interface])
# up interfaces
for interface in interfaces:
result = self.run_function('ip.up', [interface])
self.assertTrue(result)
info = self.run_function('ip.get_interfaces_details')
for interface in info['interfaces']:
self.assertTrue(self.__connected(interface))
@destructiveTest
def test_set_dhcp_linklocal_all(self):
interfaces = self.__interfaces()
for interface in interfaces:
result = self.run_function('ip.set_dhcp_linklocal_all', [interface])
self.assertTrue(result)
info = self.run_function('ip.get_interfaces_details')
for interface in info['interfaces']:
self.assertEqual(interface['ipv4']['requestmode'], 'dhcp_linklocal')
@destructiveTest
def test_static_all(self):
interfaces = self.__interfaces()
for interface in interfaces:
result = self.run_function('ip.set_static_all', [interface, '192.168.10.4', '255.255.255.0', '192.168.10.1', '8.8.4.4 my.dns.com'])
self.assertTrue(result)
info = self.run_function('ip.get_interfaces_details')
for interface in info['interfaces']:
self.assertIn('8.8.4.4', interface['ipv4']['dns'])
self.assertIn('my.dns.com', interface['ipv4']['dns'])
self.assertEqual(interface['ipv4']['requestmode'], 'static')
self.assertEqual(interface['ipv4']['address'], '192.168.10.4')
self.assertEqual(interface['ipv4']['netmask'], '255.255.255.0')
self.assertEqual(interface['ipv4']['gateway'], '192.168.10.1')
if __name__ == '__main__':
from integration import run_tests
run_tests(Nilrt_ipModuleTest)