From bd4cbd3e5ee19e211f05c10e24bee2fc0381f3f8 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 23 Apr 2017 02:02:32 +0200 Subject: [PATCH 001/342] [airos] add stub implementation for AirOS backedn --- netjsonconfig/backends/airos/airos.py | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 netjsonconfig/backends/airos/airos.py diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py new file mode 100644 index 000000000..582e34181 --- /dev/null +++ b/netjsonconfig/backends/airos/airos.py @@ -0,0 +1,37 @@ +import re + +from . import renderers +from ..base import BaseBackend +from .schema import schema + +class AirOS(BaseBackend): + """ + AirOS backend + """ + + # backend schema validator + schema = schema + + # the environment where airos + # templates lives + env_path = 'netjsonconfig.backend.airos' + + # list of renderers available + # for this backend + renderers = [ + ] + + @classmethod + def get_renderers(cls): + pass + + def _generate_contents(self, tar): + """ + Add configuration files rendered by backend + to tarfile instance + + :param tar: tarfile instance + :returns: None + """ + pass + From 5c0dd40922b316154f543bca56b4d20fc831d00c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 23 Apr 2017 02:04:18 +0200 Subject: [PATCH 002/342] [airos] add stub implementation for BaseAirOSRenderer --- netjsonconfig/backends/airos/renderers.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 netjsonconfig/backends/airos/renderers.py diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py new file mode 100644 index 000000000..a3e7fec59 --- /dev/null +++ b/netjsonconfig/backends/airos/renderers.py @@ -0,0 +1,7 @@ + +from ..base import BaseRenderer + +def BaseAirOSRenderer(BaseRenderer): + + def cleanup(self, output): + return output From d2e62ffdad9679431b3644218ee1d17022e5222d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 23 Apr 2017 02:25:44 +0200 Subject: [PATCH 003/342] [airos] draft for section resolv in SystemRenderer --- netjsonconfig/backends/airos/renderers.py | 12 ++++++++++++ netjsonconfig/backends/airos/templates/resolv.jinja2 | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 netjsonconfig/backends/airos/templates/resolv.jinja2 diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index a3e7fec59..eb9dcd38c 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -5,3 +5,15 @@ def BaseAirOSRenderer(BaseRenderer): def cleanup(self, output): return output + + +class SystemRenderer(BaseAirOSRenderer): + """ + Write configuration for + - resolv + - system + - users + """ + + def _get_resolv(self): + dns_server = self.config.get('dns_server', []) diff --git a/netjsonconfig/backends/airos/templates/resolv.jinja2 b/netjsonconfig/backends/airos/templates/resolv.jinja2 new file mode 100644 index 000000000..aec0a0b7e --- /dev/null +++ b/netjsonconfig/backends/airos/templates/resolv.jinja2 @@ -0,0 +1,8 @@ +{% if not is_empty %} +resolv.status=disable +resolv.nameserver.status=enabled + {% for i, entry in enumerate(resolv) %} + resolv.nameserver.{{ i + 1 }}.status=enabled + resolv.nameserver.{{ i + 1}}.ip=entry + {% endfor %} +{% endif %} From 5c670e1a3c64b5ba3c28a50e616efb6efce4db52 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 23 Apr 2017 11:35:36 +0200 Subject: [PATCH 004/342] [airos] fix class definition for AirOSBaseRenderer --- netjsonconfig/backends/airos/renderers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index eb9dcd38c..bbb7c1f93 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -1,7 +1,7 @@ from ..base import BaseRenderer -def BaseAirOSRenderer(BaseRenderer): +class BaseAirOSRenderer(BaseRenderer): def cleanup(self, output): return output From 20df03597a78656947cec6b41529dbc4c996447c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 23 Apr 2017 11:36:15 +0200 Subject: [PATCH 005/342] [airos] fix typo in airos jinja templates environments --- netjsonconfig/backends/airos/airos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 582e34181..4cceb935e 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -14,7 +14,7 @@ class AirOS(BaseBackend): # the environment where airos # templates lives - env_path = 'netjsonconfig.backend.airos' + env_path = 'netjsonconfig.backends.airos' # list of renderers available # for this backend From b5dfade31311ac2c2166e21ef41af5a917eb6877 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 23 Apr 2017 11:37:44 +0200 Subject: [PATCH 006/342] [bin] add AirOS backend to available backends Use AirOS backend in netjsonconfig binary --- netjsonconfig/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netjsonconfig/__init__.py b/netjsonconfig/__init__.py index 7e5e3c872..c068fdda6 100644 --- a/netjsonconfig/__init__.py +++ b/netjsonconfig/__init__.py @@ -3,3 +3,4 @@ from .backends.openwrt.openwrt import OpenWrt # noqa from .backends.openwisp.openwisp import OpenWisp # noqa from .backends.openvpn.openvpn import OpenVpn # noqa +from .backends.airos.airos import AirOS # noqa From b8abaf6de2318af0b2290a7cdfcc15baed56ff75 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 23 Apr 2017 11:39:09 +0200 Subject: [PATCH 007/342] [airos][test] add test for SystemRenderer, resolv section --- tests/airos/test_system.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/airos/test_system.py diff --git a/tests/airos/test_system.py b/tests/airos/test_system.py new file mode 100644 index 000000000..d9259e7da --- /dev/null +++ b/tests/airos/test_system.py @@ -0,0 +1,23 @@ +import unittest + +from netjsonconfig import AirOS +from netjsonconfig.exceptions import ValidationError +from netjsonconfig.utils import _TabsMixin + + +class TestSystemRenderer(unittest.TestCase, _TabsMixin): + """ + tests for backends.airos.renderers.SystemRenderer + """ + def test_resolv(self): + o = AirOS({ + "dns_server": [ + "10.150.42.1" + ] + }) + expected = self._tabs("""resolv.status=disabled +resolv.nameserver.status=enabled +resolv.nameserver.1.status=enabled +resolv.nameserver.1.ip=10.150.42.1 +""") + self.assertEqual(o.render(), expected) From 7470f72b807301a62b0bbd29e230a86435f3638d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 15 May 2017 17:42:29 +0200 Subject: [PATCH 008/342] [airos] rename template to match SystemRenderer expectation --- .../backends/airos/templates/{resolv.jinja2 => system.jinja2} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename netjsonconfig/backends/airos/templates/{resolv.jinja2 => system.jinja2} (100%) diff --git a/netjsonconfig/backends/airos/templates/resolv.jinja2 b/netjsonconfig/backends/airos/templates/system.jinja2 similarity index 100% rename from netjsonconfig/backends/airos/templates/resolv.jinja2 rename to netjsonconfig/backends/airos/templates/system.jinja2 From afbd23b67a4c0eff8722bafc6a7795ce7cb21adf Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 15 May 2017 17:46:08 +0200 Subject: [PATCH 009/342] [airos] make airos directory a python module --- netjsonconfig/backends/airos/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 netjsonconfig/backends/airos/__init__.py diff --git a/netjsonconfig/backends/airos/__init__.py b/netjsonconfig/backends/airos/__init__.py new file mode 100644 index 000000000..e69de29bb From ad6bbb7d055d9a144aeea42fa74867e5d13698bd Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 15 May 2017 17:47:40 +0200 Subject: [PATCH 010/342] [airos] add stub NetJSON schema to airos module --- netjsonconfig/backends/airos/schema.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 netjsonconfig/backends/airos/schema.py diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py new file mode 100644 index 000000000..e8133b70c --- /dev/null +++ b/netjsonconfig/backends/airos/schema.py @@ -0,0 +1,8 @@ +""" +AirOS specific JSON-Schema definition +""" +from ...schema import schema as default_schema +from ...schema import DEFAULT_FILE_MODE # noqa - backward compatibility +from ...utils import merge_config + +schema = merge_config(default_schema, {}) From a361758fd12e96a87723dab5fe4b3f453423c6f1 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 15 May 2017 17:50:17 +0200 Subject: [PATCH 011/342] [airos] enable airos backend in netjsonconfig tool --- bin/netjsonconfig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/netjsonconfig b/bin/netjsonconfig index 317da7b54..d62efbe07 100644 --- a/bin/netjsonconfig +++ b/bin/netjsonconfig @@ -56,10 +56,10 @@ output = parser.add_argument_group('output') output.add_argument('--backend', '-b', required=True, - choices=['openwrt', 'openwisp', 'openvpn'], + choices=['openwrt', 'openwisp', 'openvpn', 'airos'], action='store', type=str, - help='Configuration backend') + help='Configuration backend: openwrt, openwisp or airos') output.add_argument('--method', '-m', required=True, @@ -169,7 +169,8 @@ method_arguments = parse_method_arguments(args.args) backends = { 'openwrt': netjsonconfig.OpenWrt, 'openwisp': netjsonconfig.OpenWisp, - 'openvpn': netjsonconfig.OpenVpn + 'openvpn': netjsonconfig.OpenVpn, + 'airos': netjsonconfig.AirOS, } backend_class = backends[args.backend] From d7a7bb17125eed12d08f3f149aee0c282ce3a70c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 17 May 2017 11:18:17 +0200 Subject: [PATCH 012/342] [airos] add system renderer to airos --- netjsonconfig/backends/airos/airos.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 4cceb935e..1320d3dfe 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -19,6 +19,7 @@ class AirOS(BaseBackend): # list of renderers available # for this backend renderers = [ + renderers.SystemRenderer ] @classmethod From 2a57b22c68d608cbb48650fb87702c011275b8d0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 17 May 2017 11:18:41 +0200 Subject: [PATCH 013/342] [airos] put nameserver lists in reverse order --- netjsonconfig/backends/airos/renderers.py | 5 ++++- netjsonconfig/backends/airos/templates/system.jinja2 | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index bbb7c1f93..b5939ade7 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -16,4 +16,7 @@ class SystemRenderer(BaseAirOSRenderer): """ def _get_resolv(self): - dns_server = self.config.get('dns_server', []) + dns_server = self.config.get('dns_servers', []) + return { + 'nameserver' : reversed(list(enumerate(dns_server))), + } diff --git a/netjsonconfig/backends/airos/templates/system.jinja2 b/netjsonconfig/backends/airos/templates/system.jinja2 index aec0a0b7e..9e9b7458c 100644 --- a/netjsonconfig/backends/airos/templates/system.jinja2 +++ b/netjsonconfig/backends/airos/templates/system.jinja2 @@ -1,8 +1,8 @@ {% if not is_empty %} resolv.status=disable resolv.nameserver.status=enabled - {% for i, entry in enumerate(resolv) %} - resolv.nameserver.{{ i + 1 }}.status=enabled - resolv.nameserver.{{ i + 1}}.ip=entry - {% endfor %} +{% for i, entry in resolv.nameserver %} +resolv.nameserver.{{ i + 1 }}.status=enabled +resolv.nameserver.{{ i + 1}}.ip={{ entry }} +{% endfor %} {% endif %} From 9d8f85a71836c6bb2344e6cc2f1bacf1c4154f2b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 17 May 2017 19:52:53 +0200 Subject: [PATCH 014/342] [airos] set renderer context for system.x.y values --- netjsonconfig/backends/airos/renderers.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index b5939ade7..46ca11bb6 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -20,3 +20,14 @@ def _get_resolv(self): return { 'nameserver' : reversed(list(enumerate(dns_server))), } + + def _get_system(self): + general = self.config.get('general', {}).copy() + if general: + general['timezone'] = general.get('timezone', 'UTC') + general['latitude'] = general.get('latitude', '') + general['longitude'] = general.get('longitude', '') + general['timestamp'] = general.get('timestamp','') + general['reset'] = general.get('reset', 'enabled') + + return general From 2880c08fbff889d3ca1dc44a169da954e4daf545 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 17 May 2017 19:55:06 +0200 Subject: [PATCH 015/342] [airos] add configuration values to system section in template --- netjsonconfig/backends/airos/templates/system.jinja2 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/netjsonconfig/backends/airos/templates/system.jinja2 b/netjsonconfig/backends/airos/templates/system.jinja2 index 9e9b7458c..634a5e174 100644 --- a/netjsonconfig/backends/airos/templates/system.jinja2 +++ b/netjsonconfig/backends/airos/templates/system.jinja2 @@ -5,4 +5,11 @@ resolv.nameserver.status=enabled resolv.nameserver.{{ i + 1 }}.status=enabled resolv.nameserver.{{ i + 1}}.ip={{ entry }} {% endfor %} + +system.timezone={{ system.timezone }} +system.longitude={{ system.longitude }} +system.latitude={{ system.latitude }} +system.date.timestamp={{ system.timestamp }} +system.button.reset={{ system.reset }} + {% endif %} From ad8c9f1f1392e7aeaeea4778b6aa463e08dc4541 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 23 May 2017 13:09:22 +0200 Subject: [PATCH 016/342] [airos] added bridge interface configuration to renderers --- netjsonconfig/backends/airos/renderers.py | 14 ++++++++++++++ .../backends/airos/templates/network.jinja2 | 15 +++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 netjsonconfig/backends/airos/templates/network.jinja2 diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index 46ca11bb6..97418ba71 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -31,3 +31,17 @@ def _get_system(self): general['reset'] = general.get('reset', 'enabled') return general + + +class NetworkRenderer(BaseAirOSRenderer): + """ + Write configuration for + - bridge + """ + + def _get_bridge(self): + interfaces = self.config.get('interfaces', []) + bridge_def = [ + i for i in interfaces if i['type'] == 'bridge' + ] + return reversed(list(enumerate(bridge_def))) diff --git a/netjsonconfig/backends/airos/templates/network.jinja2 b/netjsonconfig/backends/airos/templates/network.jinja2 new file mode 100644 index 000000000..83060583f --- /dev/null +++ b/netjsonconfig/backends/airos/templates/network.jinja2 @@ -0,0 +1,15 @@ +{% if not is_empty %} + + {% for i, entry in bridge %} + bridge.{{ i + 1}}.comment= + bridge.{{ i + 1}}.port.1.devname={{ entry.bridge_members[0] }} + bridge.{{ i + 1}}.port.1.status=enabled + bridge.{{ i + 1}}.port.2.devname={{ entry.bridge_members[1] }} + bridge.{{ i + 1}}.port.2.status=enabled + bridge.{{ i + 1}}.status=enabled + bridge.{{ i + 1}}.stp.status={{ entry.stp | default("disabled") }} + {% endfor %} + + bridge.status=enabled + +{% endif %} From a859c06d72bd941564a1a95dec1af90e810a6c79 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 23 May 2017 13:10:30 +0200 Subject: [PATCH 017/342] [airos] use NetworkRenderer in AirOS backend --- netjsonconfig/backends/airos/airos.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 1320d3dfe..d45ae2bd5 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -19,7 +19,8 @@ class AirOS(BaseBackend): # list of renderers available # for this backend renderers = [ - renderers.SystemRenderer + renderers.SystemRenderer, + renderers.NetworkRenderer, ] @classmethod From 0b0e1d0737c51f8a5b28b9509e166663d51bbb31 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 23 May 2017 13:13:51 +0200 Subject: [PATCH 018/342] [airos] add bridge interface name to template --- netjsonconfig/backends/airos/templates/network.jinja2 | 1 + 1 file changed, 1 insertion(+) diff --git a/netjsonconfig/backends/airos/templates/network.jinja2 b/netjsonconfig/backends/airos/templates/network.jinja2 index 83060583f..7a5c8de5b 100644 --- a/netjsonconfig/backends/airos/templates/network.jinja2 +++ b/netjsonconfig/backends/airos/templates/network.jinja2 @@ -2,6 +2,7 @@ {% for i, entry in bridge %} bridge.{{ i + 1}}.comment= + bridge.{{ i + 1}}.devname={{ entry.name }} bridge.{{ i + 1}}.port.1.devname={{ entry.bridge_members[0] }} bridge.{{ i + 1}}.port.1.status=enabled bridge.{{ i + 1}}.port.2.devname={{ entry.bridge_members[1] }} From daa52054dcecd18138df8ffe84d61ca5b6d99f21 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 24 May 2017 11:28:15 +0200 Subject: [PATCH 019/342] [airos] added template and context for vlans --- netjsonconfig/backends/airos/renderers.py | 18 ++++++++++++++++++ .../backends/airos/templates/network.jinja2 | 7 +++++++ 2 files changed, 25 insertions(+) diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index 97418ba71..3ce78d3ab 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -37,6 +37,7 @@ class NetworkRenderer(BaseAirOSRenderer): """ Write configuration for - bridge + - vlan """ def _get_bridge(self): @@ -45,3 +46,20 @@ def _get_bridge(self): i for i in interfaces if i['type'] == 'bridge' ] return reversed(list(enumerate(bridge_def))) + + def _get_vlan(self): + interfaces = self.config.get('interfaces', []) + vlan_def = [ + i for i in interfaces if '.' in i['name'] + ] + + airos_vlan = [] + + for v in vlan_def: + airos_v = v.copy() + airos_v['devname'] = v['name'].split('.')[0] + airos_v['id'] = v['name'].split('.')[1] + airos_v['status'] = "enabled" if not v['disabled'] else "disabled" + airos_vlan.append(airos_v) + + return enumerate(airos_vlan) diff --git a/netjsonconfig/backends/airos/templates/network.jinja2 b/netjsonconfig/backends/airos/templates/network.jinja2 index 7a5c8de5b..34cfd3469 100644 --- a/netjsonconfig/backends/airos/templates/network.jinja2 +++ b/netjsonconfig/backends/airos/templates/network.jinja2 @@ -13,4 +13,11 @@ bridge.status=enabled + {% for i, entry in vlan %} + vlan.{{ i + 1}}.comment={{ entry.comment | default("") }} + vlan.{{ i + 1}}.devname={{ entry.devname }} + vlan.{{ i + 1}}.id={{ entry.id }} + vlan.{{ i + 1}}.status={{ entry.status }} + {% endfor %} + {% endif %} From 63395cd27f220152b988d728159c8c09c39f7943 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 25 May 2017 12:54:27 +0200 Subject: [PATCH 020/342] [airos] added output formatting to renderers --- netjsonconfig/backends/airos/renderers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index 3ce78d3ab..28395b66b 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -4,7 +4,10 @@ class BaseAirOSRenderer(BaseRenderer): def cleanup(self, output): - return output + stripped = [ + a.strip() for a in output.splitlines() if a.strip() + ] + return '\n'.join(stripped) class SystemRenderer(BaseAirOSRenderer): From a62624ae8883f2c1dff415bdc3655756ddfaf7a0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 28 May 2017 18:50:20 +0200 Subject: [PATCH 021/342] [airos] use intermediate representation in airos backend --- netjsonconfig/backends/airos/airos.py | 52 +++++++++++-------- netjsonconfig/backends/airos/converters.py | 59 ++++++++++++++++++++++ netjsonconfig/backends/airos/renderers.py | 8 +-- 3 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 netjsonconfig/backends/airos/converters.py diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index d45ae2bd5..21a9a8f4e 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -1,7 +1,8 @@ import re -from . import renderers -from ..base import BaseBackend +from .converters import * +from .renderers import AirOS +from ..base.backend import BaseBackend from .schema import schema class AirOS(BaseBackend): @@ -12,28 +13,37 @@ class AirOS(BaseBackend): # backend schema validator schema = schema + # converters from configuration + # dictionary to intermediate representation + converters = [ + Dns, + Vlan, + ] + # the environment where airos # templates lives env_path = 'netjsonconfig.backends.airos' + # TODO: remove # list of renderers available # for this backend - renderers = [ - renderers.SystemRenderer, - renderers.NetworkRenderer, - ] - - @classmethod - def get_renderers(cls): - pass - - def _generate_contents(self, tar): - """ - Add configuration files rendered by backend - to tarfile instance - - :param tar: tarfile instance - :returns: None - """ - pass - +# renderers = [ +# renderers.SystemRenderer, +# renderers.NetworkRenderer, +# ] + + renderer = AirOS + +# @classmethod +# def get_renderers(cls): +# pass +# +# def _generate_contents(self, tar): +# """ +# Add configuration files rendered by backend +# to tarfile instance +# +# :param tar: tarfile instance +# :returns: None +# """ +# pass diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py new file mode 100644 index 000000000..6dee9eb18 --- /dev/null +++ b/netjsonconfig/backends/airos/converters.py @@ -0,0 +1,59 @@ +from ...utils import get_copy, sorted_dict +from ..base.converter import BaseConverter + +class Dns(BaseConverter): + netjson_key = 'dns_servers' + + def to_intermediate(self): + result = [] + original = list(enumerate(get_copy(self.netjson, self.netjson_key))) + for index, nameserver in reversed(original): + result.append({ + 'key' : 'nameserver.{index}.ip'.format(index= index + 1 ), + 'value' : nameserver, + }) + result.append({ + 'key': 'nameserver.{index}.status'.format(index= index + 1), + 'value': 'enabled', + }) + + result.append({ + 'key': 'status', + 'value': 'enabled', + }) + + return (('resolv', result),) + +class Vlan(BaseConverter): + netjson_key = 'interfaces' + + def to_intermediate(self): + result = [] + original = list(enumerate([ + i for i in get_copy(self.netjson, self.netjson_key) if '.' in i['name'] + ])) + + for index, v in original: + result.append({ + 'key' : '{index}.comment'.format(index= index + 1 ), + 'value' : v.get('comment', ''), + }) + result.append({ + 'key' : '{index}.devname'.format(index= index + 1 ), + 'value' : v['name'].split('.')[0], + }) + result.append({ + 'key' : '{index}.id'.format(index= index + 1 ), + 'value' : v['name'].split('.')[1], + }) + result.append({ + 'key' : '{index}.status'.format(index= index + 1 ), + 'value' : 'disabled' if v['disabled'] else 'enabled', + }) + + result.append({ + 'key': 'status', + 'value': 'enabled', + }) + + return (('vlan', result),) diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index 28395b66b..36dccca74 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -1,7 +1,7 @@ -from ..base import BaseRenderer +from ..base.renderer import BaseRenderer -class BaseAirOSRenderer(BaseRenderer): +class AirOS(BaseRenderer): def cleanup(self, output): stripped = [ @@ -10,7 +10,7 @@ def cleanup(self, output): return '\n'.join(stripped) -class SystemRenderer(BaseAirOSRenderer): +class SystemRenderer(AirOS): """ Write configuration for - resolv @@ -36,7 +36,7 @@ def _get_system(self): return general -class NetworkRenderer(BaseAirOSRenderer): +class NetworkRenderer(AirOS): """ Write configuration for - bridge From 2afd760b36603ba6ede0f791400ddefd503090dd Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 31 May 2017 16:15:25 +0200 Subject: [PATCH 022/342] [airos] add all converters to backend --- netjsonconfig/backends/airos/airos.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 21a9a8f4e..c151d8312 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -16,8 +16,13 @@ class AirOS(BaseBackend): # converters from configuration # dictionary to intermediate representation converters = [ - Dns, + Bridge, + Gui, + Httpd, + Resolv, + Snmp, Vlan, + Wireless, ] # the environment where airos From 65d0aa2138edea3835fe56abbf6b93e4ef5bceb4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 31 May 2017 16:16:00 +0200 Subject: [PATCH 023/342] [airos] first draft for airos converters --- netjsonconfig/backends/airos/converters.py | 174 ++++++++++++++++++++- 1 file changed, 173 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 6dee9eb18..2a9aabd7d 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -1,11 +1,102 @@ from ...utils import get_copy, sorted_dict from ..base.converter import BaseConverter -class Dns(BaseConverter): +class Bridge(BaseConverter): + netjson_key = 'interfaces' + + def to_intermediate(self): + result = [] + original = list(enumerate([ + i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'bridge' + ])) + + for index, interface in original: + result.append({ + 'key' : '{n}.comment'.format(n= index + 1 ), + 'value' : interface.get('comment', ''), + }) + result.append({ + 'key' : '{n}.devname'.format(n= index + 1 ), + 'value' : interface['name'], + }) + + for port_index, port in enumerate(interface.get('bridge_members',[])): + result.append({ + 'key': '{n}.port.{m}.devname'.format(n= index + 1, m= port_index + 1), + 'value': port, + }) + result.append({ + 'key': '{n}.port.{m}.status'.format(n= index + 1, m= port_index + 1), + 'value': 'enabled', + }) + + result.append({ + 'key': 'status', + 'value': 'enabled', + }) + + return (('bridge', result),) + + + +class Gui(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [ + { + 'key': 'language', + 'value': 'it_IT', + }, + { + 'key': 'network.advanced.status', + 'value': 'enabled', + } + ] + return (('gui', result),) + + +class Httpd(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [ + { + 'key': 'https.port', + 'value': 443, + }, + { + 'key': 'https.status', + 'value': 'enabled', + }, + { + 'key': 'port', + 'value': 80, + }, + { + 'key': 'session.timeout', + 'value': 900, + }, + { + 'key': 'status', + 'value': 'enabled', + }, + ] + + return (('httpd', result),) + + +class Resolv(BaseConverter): netjson_key = 'dns_servers' def to_intermediate(self): result = [] + + result.append({ + 'key': 'host.1.name', + 'value': '', + }) + original = list(enumerate(get_copy(self.netjson, self.netjson_key))) for index, nameserver in reversed(original): result.append({ @@ -24,6 +115,34 @@ def to_intermediate(self): return (('resolv', result),) + +class Snmp(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [ + { + 'key': 'community', + 'value': 'public', + }, + { + 'key': 'contact', + 'value': '', + }, + { + 'key': 'location', + 'value': '', + }, + { + 'key': 'status', + 'value': 'enabled', + }, + ] + + return (('snmp', result),) + + + class Vlan(BaseConverter): netjson_key = 'interfaces' @@ -57,3 +176,56 @@ def to_intermediate(self): }) return (('vlan', result),) + + +class Wireless(BaseConverter): + netjson_key = 'interfaces' + + def to_intermediate(self): + result = [] + original = list(enumerate([ + i for i in get_copy(self.netjson, self.netjson_key) if hasattr(i,'wireless') + ])) + + for index, w in original: + result.append({ + 'key' : '{index}.addmtikie'.format(index= index + 1 ), + 'value' : 'enabled', + }) + result.append({ + 'key' : '{index}.devname'.format(index= index + 1 ), + 'value' : w['name'], + }) + + hide_ssid = 'enabled' if w['wireless']['hide_ssid'] else 'disabled' + result.append({ + 'key' : '{index}.hide_ssid'.format(index= index + 1 ), + 'value' : hide_ssid + }) + + encryption = w['wireless'].get('encryption', 'none') + result.append({ + 'key' : '{index}.security.type'.format(index= index + 1 ), + 'value' : encryption, + }) + + result.append({ + 'key' : '{index}.ssid'.format(index= index + 1 ), + 'value' : w['wireless']['ssid'], + }) + + result.append({ + 'key' : '{index}.status'.format(index= index + 1 ), + 'value' : 'enabled', + }) + + result.append({ + 'key' : '{index}.wds.status'.format(index= index + 1 ), + 'value' : 'enabled', + }) + result.append({ + 'key': 'status', + 'value': 'enabled', + }) + + return (('wireless', result),) From 2f7aeeb00b7ea63dee90a7ca0bb7daa0d3525cf1 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 31 May 2017 16:17:47 +0200 Subject: [PATCH 024/342] [airos] add template for airos renderer --- netjsonconfig/backends/airos/templates/airos.jinja2 | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 netjsonconfig/backends/airos/templates/airos.jinja2 diff --git a/netjsonconfig/backends/airos/templates/airos.jinja2 b/netjsonconfig/backends/airos/templates/airos.jinja2 new file mode 100644 index 000000000..a8cdf8f86 --- /dev/null +++ b/netjsonconfig/backends/airos/templates/airos.jinja2 @@ -0,0 +1,5 @@ +{% for namespace, value in data.items() %} + {% for config in value %} + {{ namespace }}.{{ config['key'] }}={{ config['value'] }} + {% endfor %} +{% endfor %} From a0668d0cfe54d05bdeda68531c21d3254d107b21 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 12:29:41 +0200 Subject: [PATCH 025/342] [airos] added function to flatten intermediate representation this function is used to flatten the intermediate representation tree to a list that only has mappings as members; the resulting members are themselves flattened to have only one level of indentation. With the resulting list we can iterate over the elements and use the key and value without having to use a recursive template --- netjsonconfig/backends/airos/airos.py | 106 ++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index c151d8312..7c8af35cf 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -52,3 +52,109 @@ class AirOS(BaseBackend): # :returns: None # """ # pass + + +def flatten(xs): + """ + Flatten a list + """ + if xs is not list: + return xs + else: + return reduce(lambda x,y: x + flatten(y), xs, []) + +def intermediate_to_list(configuration): + """ + Explore the configuration tree and flatten where + possible with the following policy + - list -> prepend the list index to every item key + - dictionary -> prepend the father key to every key + + configuration :: list + return list + + >>> intermediate_to_list([ + { + 'spam': { + 'eggs': 'spam and eggs' + } + } + ]) + >>> + [{ + 'spam.eggs' : 'spam and eggs' + ]} + + >>> intermediate_to_list([ + { + 'spam': { + 'eggs': 'spam and eggs' + } + }, + [ + { + 'henry': 'the first' + }, + { + 'jacob' : 'the second' + } + ] + ]) + >>> + [ + { + 'spam.eggs' : 'spam and eggs' + }, + { + '1.henry' : 'the first' + }, + { + '2.jacob' : 'the second' + } + ] + """ + + result = [] + + for element in configuration: + if isinstance(element, list): + result = result + intermediate_to_list(list(enumerate(element))) + + elif isinstance(element, tuple): + (index, config) = element + # update the keys to prefix the index + for k, v in config.items(): + # write the new key + config['{i}.{key}'.format(i=index + 1, key=k)] = v + # delete the old + del config[k] + # now the keys are updated with the index + # reduce to atoms the new config + # by recursively calling yourself + # on a list containing the new atom + result = result + intermediate_to_list([config]) + + elif isinstance(element, dict): + for k, v in element.items(): + if isinstance(v, str) or isinstance(v, int) or isinstance(v, unicode): + pass + else: + # reduce to atom list + # as v could be dict or list + # enclose it in a flattened list + for son in intermediate_to_list(flatten([v])): + + for sk, sv in son.items(): + nested_key = '{key}.{subkey}'.format(key=k, subkey=sk) + element[nested_key] = sv + + # remove the nested object + del element[k] + + # now it is atomic, append it to + result.append(element) + + else: + raise Exception('malformed intermediate representation') + + return result From 8b29db337f639a0108dabe0522330396bac60ad0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 12:33:40 +0200 Subject: [PATCH 026/342] [airos] updated AirOS to use the custom intermediate representation --- netjsonconfig/backends/airos/airos.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 7c8af35cf..775e5f306 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -53,6 +53,10 @@ class AirOS(BaseBackend): # """ # pass + def to_intermediate(self): + super(AirOS, self).to_intermediate() + for k,v in self.intermediate_data.items(): + self.intermediate_data[k] = flatten(intermediate_to_list(v)) def flatten(xs): """ From 38b2376b8feebd72b08e299d20f444cdd113330e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 12:36:35 +0200 Subject: [PATCH 027/342] [airos] updated converters to use the tree based intermediate configuration --- netjsonconfig/backends/airos/converters.py | 202 ++++++++------------- 1 file changed, 77 insertions(+), 125 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 2a9aabd7d..588f2408d 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -6,33 +6,29 @@ class Bridge(BaseConverter): def to_intermediate(self): result = [] - original = list(enumerate([ + original = [ i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'bridge' - ])) + ] + + bridges = [] + for interface in original: + bridge_ports = [] + for port in interface.get('bridge_members',[]): + bridge_ports.append({ + 'devname' : port, + 'status' : 'enabled', + }) - for index, interface in original: - result.append({ - 'key' : '{n}.comment'.format(n= index + 1 ), - 'value' : interface.get('comment', ''), - }) - result.append({ - 'key' : '{n}.devname'.format(n= index + 1 ), - 'value' : interface['name'], + bridges.append({ + 'comment' : interface.get('comment', ''), + 'devname' : interface['name'], + 'port' : bridge_ports, }) - for port_index, port in enumerate(interface.get('bridge_members',[])): - result.append({ - 'key': '{n}.port.{m}.devname'.format(n= index + 1, m= port_index + 1), - 'value': port, - }) - result.append({ - 'key': '{n}.port.{m}.status'.format(n= index + 1, m= port_index + 1), - 'value': 'enabled', - }) + result.append(bridges) result.append({ - 'key': 'status', - 'value': 'enabled', + 'status': 'enabled', }) return (('bridge', result),) @@ -45,12 +41,14 @@ class Gui(BaseConverter): def to_intermediate(self): result = [ { - 'key': 'language', - 'value': 'it_IT', + 'language': 'it_IT', }, { - 'key': 'network.advanced.status', - 'value': 'enabled', + 'network' : { + 'advanced' : { + 'status' : 'enabled', + } + } } ] return (('gui', result),) @@ -62,24 +60,17 @@ class Httpd(BaseConverter): def to_intermediate(self): result = [ { - 'key': 'https.port', - 'value': 443, + 'https' : { + 'port' : 443, + 'status' : 'enabled', + }, }, { - 'key': 'https.status', - 'value': 'enabled', - }, - { - 'key': 'port', - 'value': 80, - }, - { - 'key': 'session.timeout', - 'value': 900, - }, - { - 'key': 'status', - 'value': 'enabled', + 'port' : 80, + 'session' : { + 'timeout' : 900, + }, + 'status': 'enabled', }, ] @@ -93,24 +84,26 @@ def to_intermediate(self): result = [] result.append({ - 'key': 'host.1.name', - 'value': '', + 'host': [{ + 'name' : '' + }] }) - original = list(enumerate(get_copy(self.netjson, self.netjson_key))) - for index, nameserver in reversed(original): - result.append({ - 'key' : 'nameserver.{index}.ip'.format(index= index + 1 ), - 'value' : nameserver, - }) - result.append({ - 'key': 'nameserver.{index}.status'.format(index= index + 1), - 'value': 'enabled', + original = get_copy(self.netjson, self.netjson_key) + + a = { + 'nameservers' : [], + } + for nameserver in original: + a['nameservers'].append({ + 'ip': nameserver, + 'status': 'enabled', }) + result.append(a) + result.append({ - 'key': 'status', - 'value': 'enabled', + 'status': 'enabled', }) return (('resolv', result),) @@ -122,20 +115,10 @@ class Snmp(BaseConverter): def to_intermediate(self): result = [ { - 'key': 'community', - 'value': 'public', - }, - { - 'key': 'contact', - 'value': '', - }, - { - 'key': 'location', - 'value': '', - }, - { - 'key': 'status', - 'value': 'enabled', + 'community' : 'public', + 'contact' : 'value', + 'location': '', + 'status': 'enabled', }, ] @@ -148,31 +131,22 @@ class Vlan(BaseConverter): def to_intermediate(self): result = [] - original = list(enumerate([ + original = [ i for i in get_copy(self.netjson, self.netjson_key) if '.' in i['name'] - ])) - - for index, v in original: - result.append({ - 'key' : '{index}.comment'.format(index= index + 1 ), - 'value' : v.get('comment', ''), - }) - result.append({ - 'key' : '{index}.devname'.format(index= index + 1 ), - 'value' : v['name'].split('.')[0], - }) - result.append({ - 'key' : '{index}.id'.format(index= index + 1 ), - 'value' : v['name'].split('.')[1], - }) - result.append({ - 'key' : '{index}.status'.format(index= index + 1 ), - 'value' : 'disabled' if v['disabled'] else 'enabled', + ] + + vlans = [] + for v in original: + vlans.append({ + 'comment' : v.get('comment', ''), + 'devname' : v['name'].split('.')[0], + 'id' : v['name'].split('.')[1], + 'status' : 'disabled' if v['disabled'] else 'enabled', }) + result.append(vlans) result.append({ - 'key': 'status', - 'value': 'enabled', + 'status': 'enabled', }) return (('vlan', result),) @@ -183,49 +157,27 @@ class Wireless(BaseConverter): def to_intermediate(self): result = [] - original = list(enumerate([ + original = [ i for i in get_copy(self.netjson, self.netjson_key) if hasattr(i,'wireless') - ])) - - for index, w in original: - result.append({ - 'key' : '{index}.addmtikie'.format(index= index + 1 ), - 'value' : 'enabled', - }) - result.append({ - 'key' : '{index}.devname'.format(index= index + 1 ), - 'value' : w['name'], - }) + ] + ws = [] + for w in original: hide_ssid = 'enabled' if w['wireless']['hide_ssid'] else 'disabled' - result.append({ - 'key' : '{index}.hide_ssid'.format(index= index + 1 ), - 'value' : hide_ssid - }) - encryption = w['wireless'].get('encryption', 'none') - result.append({ - 'key' : '{index}.security.type'.format(index= index + 1 ), - 'value' : encryption, - }) - - result.append({ - 'key' : '{index}.ssid'.format(index= index + 1 ), - 'value' : w['wireless']['ssid'], - }) - - result.append({ - 'key' : '{index}.status'.format(index= index + 1 ), - 'value' : 'enabled', + ws.append({ + 'addmtikie': 'enabled', + 'devname' : w['name'], + 'hide_ssid' : hide_ssid, + 'security' : { 'type' : encryption }, + 'ssid' : w['wireless']['ssid'], + 'status' : 'enabled', + 'wds' : { 'status': 'enabled' }, }) + result.append(ws) - result.append({ - 'key' : '{index}.wds.status'.format(index= index + 1 ), - 'value' : 'enabled', - }) result.append({ - 'key': 'status', - 'value': 'enabled', + 'status': 'enabled', }) return (('wireless', result),) From 2a82f2046e4ede526823c0bbbc64a160bc0d475f Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 12:37:22 +0200 Subject: [PATCH 028/342] [airos] simplified template to use tree intermediate representation --- netjsonconfig/backends/airos/templates/airos.jinja2 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/netjsonconfig/backends/airos/templates/airos.jinja2 b/netjsonconfig/backends/airos/templates/airos.jinja2 index a8cdf8f86..a50795b53 100644 --- a/netjsonconfig/backends/airos/templates/airos.jinja2 +++ b/netjsonconfig/backends/airos/templates/airos.jinja2 @@ -1,5 +1,7 @@ -{% for namespace, value in data.items() %} - {% for config in value %} - {{ namespace }}.{{ config['key'] }}={{ config['value'] }} +{% for namespace, block in data.items() %} + {% for element in block %} + {% for k,v in element.items() %} + {{ namespace }}.{{ k }} = {{ v }} + {% endfor %} {% endfor %} {% endfor %} From c7077b4e7d5b09d8593081e3cb3c9eff98cb3556 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 12:48:32 +0200 Subject: [PATCH 029/342] [airos] removed unnecessary methods and properties from backends --- netjsonconfig/backends/airos/airos.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 775e5f306..d624fba01 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -29,30 +29,8 @@ class AirOS(BaseBackend): # templates lives env_path = 'netjsonconfig.backends.airos' - # TODO: remove - # list of renderers available - # for this backend -# renderers = [ -# renderers.SystemRenderer, -# renderers.NetworkRenderer, -# ] - renderer = AirOS -# @classmethod -# def get_renderers(cls): -# pass -# -# def _generate_contents(self, tar): -# """ -# Add configuration files rendered by backend -# to tarfile instance -# -# :param tar: tarfile instance -# :returns: None -# """ -# pass - def to_intermediate(self): super(AirOS, self).to_intermediate() for k,v in self.intermediate_data.items(): From eb06d2df93f14820b48b7b5ada546dee93093057 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 13:15:36 +0200 Subject: [PATCH 030/342] [airos] close #13 --- netjsonconfig/backends/airos/airos.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index d624fba01..bdb5e0c9a 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -1,5 +1,7 @@ import re +from six import string_types + from .converters import * from .renderers import AirOS from ..base.backend import BaseBackend @@ -118,7 +120,7 @@ def intermediate_to_list(configuration): elif isinstance(element, dict): for k, v in element.items(): - if isinstance(v, str) or isinstance(v, int) or isinstance(v, unicode): + if isinstance(v, string_types) or isinstance(v, int): pass else: # reduce to atom list From f94d2b7b56df953312cc7e6c7dd9883ae0cf9c96 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 17:17:10 +0200 Subject: [PATCH 031/342] [airos][test] added test for vlan converter --- tests/airos/dummy.py | 9 ++ tests/airos/test_vlan.py | 205 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 tests/airos/dummy.py create mode 100644 tests/airos/test_vlan.py diff --git a/tests/airos/dummy.py b/tests/airos/dummy.py new file mode 100644 index 000000000..d43f88a4d --- /dev/null +++ b/tests/airos/dummy.py @@ -0,0 +1,9 @@ +from netjsonconfig import AirOS + +from netjsonconfig.backends.airos.converters import * + + +class VlanAirOS(AirOS): + converters = [ + Vlan, + ] diff --git a/tests/airos/test_vlan.py b/tests/airos/test_vlan.py new file mode 100644 index 000000000..79d407a36 --- /dev/null +++ b/tests/airos/test_vlan.py @@ -0,0 +1,205 @@ +import unittest + +from collections import OrderedDict + +from netjsonconfig.backends.airos.converters import * +from netjsonconfig.exceptions import ValidationError + +from .dummy import VlanAirOS + + +class TestVlanConverter(unittest.TestCase): + """ + tests for backends.airos.renderers.SystemRenderer + """ + backend = VlanAirOS + + def test_active_vlan(self): + + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0.1", + "disabled": False, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status' : 'enabled', + }, + { + 'status' : 'enabled', + } + ] + + self.assertEqual(o.intermediate_data['vlan'], expected) + + def test_disabled_vlan(self): + + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0.1", + "disabled": True, + } + ] + }) + + expected = [ + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status': 'disabled', + }, + { + 'status' : 'enabled', + } + ] + + o.to_intermediate() + + self.assertEqual(o.intermediate_data['vlan'], expected) + + def test_many_vlan(self): + + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0.1", + "disabled": False, + }, + { + "type": "ethernet", + "name": "eth0.2", + "disabled": False, + } + ] + }) + + expected = [ + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status': 'enabled', + }, + { + '2.comment': '', + '2.devname': 'eth0', + '2.id': '2', + '2.status': 'enabled', + }, + { + 'status' : 'enabled', + } + ] + + o.to_intermediate() + + self.assertEqual(o.intermediate_data['vlan'], expected) + + def test_mixed_vlan(self): + + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0.1", + "disabled": True, + }, + { + "type": "ethernet", + "name": "eth0.2", + "disabled": False, + } + ] + }) + + expected = [ + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status': 'disabled', + }, + { + '2.comment': '', + '2.devname': 'eth0', + '2.id': '2', + '2.status': 'enabled', + }, + { + 'status' : 'enabled', + } + ] + + o.to_intermediate() + + self.assertEqual(o.intermediate_data['vlan'], expected) + + def test_no_vlan(self): + + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0", + "disabled": True, + }, + ] + }) + + expected = [ + { + 'status' : 'enabled', + }, + ] + + o.to_intermediate() + + self.assertEqual(o.intermediate_data['vlan'], expected) + + def test_one_vlan(self): + + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0", + "disabled": False, + }, + { + "type": "ethernet", + "name": "eth0.1", + "disabled": False, + }, + + ] + }) + + expected = [ + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status': 'enabled', + }, + { + 'status' : 'enabled', + }, + ] + + o.to_intermediate() + + self.assertEqual(o.intermediate_data['vlan'], expected) From ec135bdb1162f8d6062431e2ade0cb79cd43f7a8 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 22:18:42 +0200 Subject: [PATCH 032/342] [airos] fixed typo in key name in intermediate representation --- netjsonconfig/backends/airos/converters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 588f2408d..9ca121593 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -92,10 +92,10 @@ def to_intermediate(self): original = get_copy(self.netjson, self.netjson_key) a = { - 'nameservers' : [], + 'nameserver' : [], } for nameserver in original: - a['nameservers'].append({ + a['nameserver'].append({ 'ip': nameserver, 'status': 'enabled', }) From 290ce9182689ffc016ab5e5f3cf41178366b2818 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 22:23:01 +0200 Subject: [PATCH 033/342] [airos][test] add tests for resolv converter --- tests/airos/dummy.py | 6 +++++ tests/airos/test_resolv.py | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 tests/airos/test_resolv.py diff --git a/tests/airos/dummy.py b/tests/airos/dummy.py index d43f88a4d..355e3467d 100644 --- a/tests/airos/dummy.py +++ b/tests/airos/dummy.py @@ -7,3 +7,9 @@ class VlanAirOS(AirOS): converters = [ Vlan, ] + + +class ResolvAirOS(AirOS): + converters = [ + Resolv, + ] diff --git a/tests/airos/test_resolv.py b/tests/airos/test_resolv.py new file mode 100644 index 000000000..13299fbb8 --- /dev/null +++ b/tests/airos/test_resolv.py @@ -0,0 +1,51 @@ +import unittest + +from .dummy import ResolvAirOS + + +class TestResolvConverter(unittest.TestCase): + + backend = ResolvAirOS + + def test_resolv(self): + o = self.backend({ + "dns_servers": [ + "10.150.42.1" + ], + }) + + o.to_intermediate() + + expected = [ + { + 'host.1.name' : '', + }, + { + 'nameserver.1.ip' : '10.150.42.1', + 'nameserver.1.status' : 'enabled', + }, + { + 'status' : 'enabled', + }, + ] + + + self.assertEqual(o.intermediate_data['resolv'], expected) + + def test_no_dns_server(self): + o = self.backend({ + "dns_servers": [], + }) + + o.to_intermediate() + + expected = [ + { + 'host.1.name' : '', + }, + { + 'status' : 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['resolv'], expected) From 086abb9ccf8a4cc5e4eaae5cb9f13958bd5a8c26 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 22:25:46 +0200 Subject: [PATCH 034/342] [airos] remove unused renderers from backend --- netjsonconfig/backends/airos/renderers.py | 60 +---------------------- 1 file changed, 1 insertion(+), 59 deletions(-) diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index 36dccca74..676e64bac 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -1,6 +1,6 @@ - from ..base.renderer import BaseRenderer + class AirOS(BaseRenderer): def cleanup(self, output): @@ -8,61 +8,3 @@ def cleanup(self, output): a.strip() for a in output.splitlines() if a.strip() ] return '\n'.join(stripped) - - -class SystemRenderer(AirOS): - """ - Write configuration for - - resolv - - system - - users - """ - - def _get_resolv(self): - dns_server = self.config.get('dns_servers', []) - return { - 'nameserver' : reversed(list(enumerate(dns_server))), - } - - def _get_system(self): - general = self.config.get('general', {}).copy() - if general: - general['timezone'] = general.get('timezone', 'UTC') - general['latitude'] = general.get('latitude', '') - general['longitude'] = general.get('longitude', '') - general['timestamp'] = general.get('timestamp','') - general['reset'] = general.get('reset', 'enabled') - - return general - - -class NetworkRenderer(AirOS): - """ - Write configuration for - - bridge - - vlan - """ - - def _get_bridge(self): - interfaces = self.config.get('interfaces', []) - bridge_def = [ - i for i in interfaces if i['type'] == 'bridge' - ] - return reversed(list(enumerate(bridge_def))) - - def _get_vlan(self): - interfaces = self.config.get('interfaces', []) - vlan_def = [ - i for i in interfaces if '.' in i['name'] - ] - - airos_vlan = [] - - for v in vlan_def: - airos_v = v.copy() - airos_v['devname'] = v['name'].split('.')[0] - airos_v['id'] = v['name'].split('.')[1] - airos_v['status'] = "enabled" if not v['disabled'] else "disabled" - airos_vlan.append(airos_v) - - return enumerate(airos_vlan) From 8cad8e765d663e8fb0a61cb6ba3533a9bee3b028 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 1 Jun 2017 23:01:49 +0200 Subject: [PATCH 035/342] [airos][test] added test for WirelessConverter --- tests/airos/test_wireless.py | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/airos/test_wireless.py diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py new file mode 100644 index 000000000..5a1509a96 --- /dev/null +++ b/tests/airos/test_wireless.py @@ -0,0 +1,57 @@ +import unittest + +from netjsonconfig.backends.airos.converters import * + +from .dummy import WirelessAirOS + + +class TestWirelessConverter(unittest.TestCase): + + backend = WirelessAirOS + + def test_active_wireless(self): + + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac" : "de:9f:db:30:c9:c5", + "mtu" : 1500, + "txqueuelen" : 1000, + "autostart" : True, + "wireless" : { + "radio" : "radio0", + "mode" : "access_point", + "ssid" : "ap-ssid-example", + }, + "addresses" : [ + { + "address" : "192.168.1.1", + "mask" : 24, + "family" : "ipv4", + "proto" : "static", + } + ], + } + ] + }) + + o.to_intermediate() + + expected = [ + { + '1.addmtikie': 'enabled', + '1.devname': 'wlan0', + '1.hide_ssid': 'disabled', + '1.security.type': 'none', + '1.ssid': 'ap-ssid-example', + '1.status': 'enabled', + '1.wds.status': 'enabled', + }, + { + 'status' : 'enabled', + } + ] + + self.assertEqual(o.intermediate_data['wireless'], expected) From 7d04c0861a16c3d7517a6b320abd1523b9395ab7 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sat, 3 Jun 2017 12:10:35 +0200 Subject: [PATCH 036/342] [airos][doc] added backend docs page for AirOS --- docs/source/backends/airos.rst | 279 +++++++++++++++++++++++++++++++++ docs/source/conf.py | 1 + docs/source/index.rst | 1 + 3 files changed, 281 insertions(+) create mode 100644 docs/source/backends/airos.rst diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst new file mode 100644 index 000000000..d034ba11d --- /dev/null +++ b/docs/source/backends/airos.rst @@ -0,0 +1,279 @@ +============= +AirOS Backend +============= + +.. include:: ../_github.rst + +The ``AirOS`` backend allows to generate AirOS compatible configurations. + +Initialization +-------------- + +.. automethod:: netjsonconfig.AirOS.__init__ + +Initialization example: + +.. code-block:: python + + from netjsonconfig import AirOS + + router = AirOS({ + "general": { + "hostname": "MasterAntenna" + } + }) + +If you are unsure about the meaning of the initalization parameters, +read about the following basic concepts: + + * :ref:`configuration_dictionary` + * :ref:`template` + * :ref:`context` + +Intermediate representation +--------------------------- + +The intermediate representation is the output of the a :ref:`converter`, +it is backend specific and is built as a tree structure made from python +builtins values + +A tree is a *acyclic, directional graph* with an element called *root*. + +The root of our tree is stored in the first element of a tuple, along with +the root's direct sons as a list + +.. code-block:: python + + tree = (root, direct_sons) + +As an example here we present the tree `('spam', ['eggs', 'snakes'])` + +.. graphviz:: + + digraph tree { + spam -> {eggs, snakes}; + } + +As a son may be a carrier of a value so we store it in a dictionary instead of adding a *leaf* +with another level of recursion + +As an example here we present the tree `('spam', [ { 'eggs': 2 }, { 'snakes' : { 'loved' : 'python' }}])` + +.. graphviz:: + + digraph tree { + + eggs[label="{ eggs : 2 }"]; + loved[label="{ loved : python }"]; + + spam -> eggs; + spam -> snakes -> loved; + + } + +This tree could be tranlated to a configuration file for AirOS that looks like this + +.. code-block:: ini + + spam.eggs=2 + spam.snakes.loved=python + + +So our tree representation is based on the simple assumption that a *leaf* is a dictionary +without nested values and nested values in a dictionary creates a father-son relationship + +Instead when the configuration requires that the son values must be prefixed from a number, +e.g. `vlan.1.devname=eth0` we store a list of dictionaries. + +.. code-block:: python + + ( + 'spam', + [ + { + 'eggs' : 2, + }, + { + 'snakes' : { + 'loved' : [ + { + 'python2' : True, + }, + { + 'python3' : True, + }, + { + 'ipython' : True, + } + ], + }, + } + ] + +And the resulting tree is this + +.. graphviz:: + + digraph tree { + + eggs[label="{ eggs : 2 }"]; + loved; + + python2[label="{ python2 : True }"]; + python3[label="{ python3 : True }"]; + ipython[label="{ ipython : True }"]; + + spam -> eggs; + spam -> snakes -> loved; + + loved -> {1,2,3}; + + 1 -> python2; + 2 -> python3; + 3 -> ipython; + + } + +And the configuration is + +.. code-block:: ini + + spam.eggs=2 + spam.snakes.loved.1.python2=true + spam.snakes.loved.2.python3=true + spam.snakes.loved.2.ipython=true + +The process by which we can go from the intermediate representation from +the output configuration is called flattening + +Flattening +---------- + +To avoid at all cost a recursive logic in the template we flatten the intermediate +representation to something that has a *namespace* a *key* and a *value* + +This input NetJSON will be converted to a python :ref:`configuration_dictionary` + +.. code-block:: json + + //netjson + { + "type" : "DeviceConfiguration", + "interfaces" : [ + { + "name" : "eth0.1", + "type" : "ethernet", + "comment" : "management vlan" + }, + { + "name" : "eth0.2", + "type" : "ethernet", + "comment" : "traffic vlan" + } + ] + } + +.. code-block:: python + + #python + { + 'interfaces' : [ + { + 'name' : 'eth0.1', + 'type' : 'ethernet', + 'comment' : 'management' + }, + { + 'name' : 'eth0.2', + 'type' : 'ethernet', + 'comment' : 'traffic' + } + ] + } + + +And this must be converted to an appropiate AirOS configuration which looks like this + +.. code-block:: ini + + vlan.1.comment=management + vlan.1.devname=eth0 + vlan.1.id=1 + vlan.1.status=enabled + vlan.2.comment=management + vlan.2.devname=eth0 + vlan.2.id=2 + vlan.2.status=enabled + vlan.status=enabled + +To do this we must convert the :ref:`configuration_dictionary` into something that +resemble the target text, the output configuration + +.. code-block:: python + + ( + # namespace + 'vlan', + #options + [ + { + # key : value + '1.devname' : 'eth0', + '1.id' : '1' + '1.status' : 'enabled', + '1.comment' : 'management' + }, + { + '2.devname' : 'eth0', + '2.id' : '2' + '2.status' : 'enabled', + '2.comment' : 'traffic' + } + ] + + ) + +And to do that we get rid of the multiple indentation levels by flattening the tree structure + +The tree associated with the previous NetJSON example is this + +.. graphviz:: + + digraph tree { + vlan -> {1,2}; + devname1 [label="devname=eth0"]; + devname2 [label="devname=eth0"]; + + id1 [label="id=1"]; + id2 [label="id=2"]; + + status1 [label="status=enabled"]; + status2 [label="status=enabled"]; + + comment1 [label="comment=management"]; + comment2 [label="comment=traffic"]; + + 1 -> {devname1, id1, status1, comment1}; + 2 -> {devname2, id2, status2, comment2}; + } + +And by exploring depth first we get to read a line of the configuration at a time. + +E.g. following the blue line from the `vlan` root to the first `leaf` we have the +configuration `vlan.1.devname=eth0` + +.. graphviz:: + + digraph tree { + vlan -> 1 [color="blue"]; + devname1 [label="devname=eth0"]; + + id1 [label="id=1"]; + + status1 [label="status=enabled"]; + + comment1 [label="comment=management"]; + + 1 -> devname1 [color="blue"]; + 1 -> {id1, status1, comment1}; + } diff --git a/docs/source/conf.py b/docs/source/conf.py index 5b3b97aab..461398200 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -35,6 +35,7 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', + 'sphinx.ext.graphviz', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/source/index.rst b/docs/source/index.rst index 110b4c96b..d8637a62d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -48,6 +48,7 @@ Contents: /general/setup /general/basics + /backends/airos /backends/openwrt /backends/openwisp /backends/openvpn From 29a0be7bbf37c9657dccb0f161c409aff01e3dec Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 5 Jun 2017 12:37:19 +0200 Subject: [PATCH 037/342] [airos] fill remaining converters for airos --- netjsonconfig/backends/airos/converters.py | 269 ++++++++++++++++++++- 1 file changed, 258 insertions(+), 11 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 9ca121593..66c76b5ff 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -1,6 +1,33 @@ from ...utils import get_copy, sorted_dict from ..base.converter import BaseConverter + +class Aaa(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + result.append([ + { + 'radius': { + 'acct': [ + { + 'port': 1813, + 'status': 'disabled', + }, + ], + 'auth': [ + { + 'port': 1812, + }, + ], + }, + 'status': 'disabled', + } + ]) + return (('aaa', result),) + class Bridge(BaseConverter): netjson_key = 'interfaces' @@ -34,6 +61,8 @@ def to_intermediate(self): return (('bridge', result),) +class Discovery(BaseConverter): + netjson_key = 'general' class Gui(BaseConverter): netjson_key = 'general' @@ -77,6 +106,94 @@ def to_intermediate(self): return (('httpd', result),) +class Netconf(BaseConverter): + netjson_key = 'interfaces' + + def to_intermediate(self): + result = [] + interfaces = [] + original = [ + i for i in get_copy(self.netjson, self.netjson_key) + ] + + for interface in original: + + interfaces.append({ + 'devname': interface['name'], + 'status': 'disabled' if interface.get('disabled', False) else 'enabled', + }) + + result.append(interfaces) + result.append({ + 'status': 'enabled', + }) + return (('netconf', result),) + + +class Netmode(BaseConverter): + netjson_key = 'interfaces' + + def to_intermediate(self): + result = [] + + result.append({ + 'status': 'enabled', + }) + return (('netmode', result), ) + + +class Ntpclient(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + result.append({ + 'status': 'enabled', + }) + return (('ntpclient',result),) + + + +class Pwdog(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + result.append({ + 'delay': 300, + 'period': 300, + 'retry': 3, + 'status': 'enabled', + }) + return (('pwdog', result),) + + +class Radio(BaseConverter): + netjson_key = 'radios' + + def to_intermediate(self): + result = [] + + original = get_copyt(self.netjson, self.netjson_key) + + radios = [] + + for r in original: + radios.append({ + 'devname': r['name'], + 'status': 'enabled', + 'txpower': r.get('tx_power',''), + }) + + result.append({ + 'status': 'enabled', + }) + + return (('radio', result),) + + class Resolv(BaseConverter): netjson_key = 'dns_servers' @@ -84,47 +201,153 @@ def to_intermediate(self): result = [] result.append({ - 'host': [{ - 'name' : '' + 'host': [{ + 'name': '' }] }) original = get_copy(self.netjson, self.netjson_key) a = { - 'nameserver' : [], + 'nameserver': [], } for nameserver in original: a['nameserver'].append({ - 'ip': nameserver, - 'status': 'enabled', + 'ip': nameserver, + 'status': 'enabled', }) result.append(a) result.append({ - 'status': 'enabled', + 'status': 'enabled', }) return (('resolv', result),) +class Route(BaseConverter): + netjson_key = 'routes' + + def to_intermediate(self): + result = [] + original = get_copy(self.netjson, self.netjson_key) + + routes = [] + + for r in original: + routes.append({ + 'devname': '', + 'gateway': '0.0.0.0', + 'ip': '0.0.0.0', + 'netmask': 0, + 'status': 'enabled', + }) + + result.append(routes) + + result.append({ + 'status': 'enabled', + }) + return (('route', result),) + + class Snmp(BaseConverter): netjson_key = 'general' def to_intermediate(self): result = [ - { - 'community' : 'public', - 'contact' : 'value', - 'location': '', - 'status': 'enabled', + { + 'community': 'public', + 'contact': 'value', + 'location': '', + 'status': 'enabled', }, ] return (('snmp', result),) +class Sshd(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + result.append({ + 'port': 22, + 'status': 'enabled', + }) + return (('sshd', result),) + +class Syslog(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + result.append({ + 'status': 'disabled', + }) + return (('syslog', result),) + + +class System(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + return (('system', result),) + + +class Telnetd(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + result.append({ + 'port': 23, + 'status': 'enabled', + }) + return (('telnetd', result),) + + +class Update(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + result.append({ + 'check': { + 'status': 'enabled', + }, + }) + return (('update', result),) + + +class Users(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + result.append({ + 'status': 'enabled', + }) + + result.append([ + { + 'name': 'root', + 'password': 'changeme', + 'status': 'enabled', + }, + ]) + + return (('users', result),) + class Vlan(BaseConverter): netjson_key = 'interfaces' @@ -181,3 +404,27 @@ def to_intermediate(self): }) return (('wireless', result),) + +class Wpasupplicant(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [] + + result.append({ + 'device': [ + { + 'status': 'disabled', + }, + ], + 'profile': [ + { + 'network': [ + { + 'ssid': 'your-ssid-here', + }, + ], + }, + ], + }) + return (('wpasupplicant', result),) From 110a524b50365762d2606ca44199d345e463107a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 5 Jun 2017 12:43:22 +0200 Subject: [PATCH 038/342] [airos] chage netjson key for gui converter --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 66c76b5ff..64dd1d633 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -65,7 +65,7 @@ class Discovery(BaseConverter): netjson_key = 'general' class Gui(BaseConverter): - netjson_key = 'general' + netjson_key = 'gui' def to_intermediate(self): result = [ From bcfcb904ce6288b8aad637f7c13a1c9317263607 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 5 Jun 2017 12:47:33 +0200 Subject: [PATCH 039/342] [airos] add dyndns and discovery converters --- netjsonconfig/backends/airos/converters.py | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 64dd1d633..08942f06e 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -64,6 +64,35 @@ def to_intermediate(self): class Discovery(BaseConverter): netjson_key = 'general' + def to_intermediate(self): + result = [ + { + 'cdp': { + 'status': 'enabled', + }, + 'status': 'enabled', + }, + ] + return (('discovery', result),) + + +class Dyndns(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [ + [ + { + 'servicename': 'dyndns.org', + }, + ], + { + 'status': 'enabled', + }, + ] + return (('dyndns', result),) + + class Gui(BaseConverter): netjson_key = 'gui' From 9f94cf661496af6c07c8ae3ec246ce564946061b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 5 Jun 2017 12:49:56 +0200 Subject: [PATCH 040/342] [airos] fix syntastic warnings --- netjsonconfig/backends/airos/converters.py | 84 ++++++++++++---------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 08942f06e..b08d159fe 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -33,6 +33,7 @@ class Bridge(BaseConverter): def to_intermediate(self): result = [] + original = [ i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'bridge' ] @@ -40,22 +41,25 @@ def to_intermediate(self): bridges = [] for interface in original: bridge_ports = [] - for port in interface.get('bridge_members',[]): + for port in interface.get('bridge_members', []): bridge_ports.append({ - 'devname' : port, - 'status' : 'enabled', + 'devname': port, + 'status': 'enabled', }) bridges.append({ - 'comment' : interface.get('comment', ''), - 'devname' : interface['name'], - 'port' : bridge_ports, + 'comment': interface.get('comment', ''), + 'devname': interface['name'], + 'port': bridge_ports, + 'status': 'disabled' if interface['disabled'] else 'enabled', + 'stp': { + 'status': 'enabled', + } }) - result.append(bridges) result.append({ - 'status': 'enabled', + 'status': 'enabled', }) return (('bridge', result),) @@ -98,13 +102,13 @@ class Gui(BaseConverter): def to_intermediate(self): result = [ - { - 'language': 'it_IT', + { + 'language': 'it_IT', }, - { - 'network' : { - 'advanced' : { - 'status' : 'enabled', + { + 'network': { + 'advanced': { + 'status': 'enabled', } } } @@ -117,18 +121,18 @@ class Httpd(BaseConverter): def to_intermediate(self): result = [ - { - 'https' : { - 'port' : 443, - 'status' : 'enabled', + { + 'https': { + 'port': 443, + 'status': 'enabled', }, }, - { - 'port' : 80, - 'session' : { - 'timeout' : 900, + { + 'port': 80, + 'session': { + 'timeout': 900, }, - 'status': 'enabled', + 'status': 'enabled', }, ] @@ -390,15 +394,15 @@ def to_intermediate(self): vlans = [] for v in original: vlans.append({ - 'comment' : v.get('comment', ''), - 'devname' : v['name'].split('.')[0], - 'id' : v['name'].split('.')[1], - 'status' : 'disabled' if v['disabled'] else 'enabled', + 'comment': v.get('comment', ''), + 'devname': v['name'].split('.')[0], + 'id': v['name'].split('.')[1], + 'status': 'disabled' if v['disabled'] else 'enabled', }) result.append(vlans) result.append({ - 'status': 'enabled', + 'status': 'enabled', }) return (('vlan', result),) @@ -410,26 +414,30 @@ class Wireless(BaseConverter): def to_intermediate(self): result = [] original = [ - i for i in get_copy(self.netjson, self.netjson_key) if hasattr(i,'wireless') + i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'wireless' ] ws = [] for w in original: - hide_ssid = 'enabled' if w['wireless']['hide_ssid'] else 'disabled' + hide_ssid = 'enabled' if w['wireless'].get('hide_ssid') else 'disabled' encryption = w['wireless'].get('encryption', 'none') ws.append({ - 'addmtikie': 'enabled', - 'devname' : w['name'], - 'hide_ssid' : hide_ssid, - 'security' : { 'type' : encryption }, - 'ssid' : w['wireless']['ssid'], - 'status' : 'enabled', - 'wds' : { 'status': 'enabled' }, + 'addmtikie': 'enabled', + 'devname': w['name'], + 'hide_ssid': hide_ssid, + 'security': { + 'type': encryption + }, + 'ssid': w['wireless']['ssid'], + 'status': 'enabled', + 'wds': { + 'status': 'enabled', + }, }) result.append(ws) result.append({ - 'status': 'enabled', + 'status': 'enabled', }) return (('wireless', result),) From 6da99c5b2b853b1953592d55b1dbb1ecb2147b02 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 5 Jun 2017 12:50:37 +0200 Subject: [PATCH 041/342] [airos] add converters to backend --- netjsonconfig/backends/airos/airos.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index bdb5e0c9a..b3b783529 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -18,13 +18,29 @@ class AirOS(BaseBackend): # converters from configuration # dictionary to intermediate representation converters = [ + Aaa, Bridge, + Discovery, + Dyndns, Gui, Httpd, + Netconf, + Netmode, + Ntpclient, + Pwdog, + Radio, Resolv, + Route, Snmp, + Sshd, + Syslog, + System, + Telnetd, + Update, + Users, Vlan, Wireless, + Wpasupplicant ] # the environment where airos From bbabb2cceac499f6a47d223b320281b16b9a8597 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 9 Jun 2017 09:08:07 +0200 Subject: [PATCH 042/342] [airos] freeze defaults for converters --- netjsonconfig/backends/airos/converters.py | 62 +++++++++++++--------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index b08d159fe..28724a1e0 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -10,19 +10,19 @@ def to_intermediate(self): result.append([ { - 'radius': { - 'acct': [ - { - 'port': 1813, - 'status': 'disabled', - }, - ], - 'auth': [ - { - 'port': 1812, - }, - ], - }, +# 'radius': { +# 'acct': [ +# { +# 'port': 1813, +# 'status': 'disabled', +# }, +# ], +# 'auth': [ +# { +# 'port': 1812, +# }, +# ], +# }, 'status': 'disabled', } ]) @@ -85,13 +85,13 @@ class Dyndns(BaseConverter): def to_intermediate(self): result = [ - [ - { - 'servicename': 'dyndns.org', - }, - ], +# [ +# { +# 'servicename': 'dyndns.org', +# }, +# ], { - 'status': 'enabled', + 'status': 'disabled', }, ] return (('dyndns', result),) @@ -103,7 +103,7 @@ class Gui(BaseConverter): def to_intermediate(self): result = [ { - 'language': 'it_IT', + 'language': 'en_US', }, { 'network': { @@ -292,7 +292,7 @@ def to_intermediate(self): result = [ { 'community': 'public', - 'contact': 'value', + 'contact': '', 'location': '', 'status': 'enabled', }, @@ -308,11 +308,15 @@ def to_intermediate(self): result = [] result.append({ + 'auth': { + 'passwd': 'enabled', + }, 'port': 22, 'status': 'enabled', }) return (('sshd', result),) + class Syslog(BaseConverter): netjson_key = 'general' @@ -331,6 +335,11 @@ class System(BaseConverter): def to_intermediate(self): result = [] + result.append({ + 'external': { + 'reset': 'enabled', + }, + }) return (('system', result),) @@ -342,7 +351,7 @@ def to_intermediate(self): result.append({ 'port': 23, - 'status': 'enabled', + 'status': 'disabled', }) return (('telnetd', result),) @@ -368,14 +377,14 @@ def to_intermediate(self): result = [] result.append({ - 'status': 'enabled', + 'status': 'disabled', }) result.append([ { 'name': 'root', 'password': 'changeme', - 'status': 'enabled', + 'status': 'disabled', }, ]) @@ -428,6 +437,11 @@ def to_intermediate(self): 'security': { 'type': encryption }, + 'signal_led1': 75, + 'signal_led2': 50, + 'signal_led3': 25, + 'signal_led4': 15, + 'signal_led_status': 'enabled', 'ssid': w['wireless']['ssid'], 'status': 'enabled', 'wds': { From 9349d03577bcce1036a965f18e617ad779177cfb Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 9 Jun 2017 09:08:34 +0200 Subject: [PATCH 043/342] [airos] fix syntastic errors --- netjsonconfig/backends/airos/converters.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 28724a1e0..e6aca5807 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -209,7 +209,7 @@ class Radio(BaseConverter): def to_intermediate(self): result = [] - original = get_copyt(self.netjson, self.netjson_key) + original = get_copy(self.netjson, self.netjson_key) radios = [] @@ -217,7 +217,7 @@ def to_intermediate(self): radios.append({ 'devname': r['name'], 'status': 'enabled', - 'txpower': r.get('tx_power',''), + 'txpower': r.get('tx_power', ''), }) result.append({ @@ -430,6 +430,7 @@ def to_intermediate(self): for w in original: hide_ssid = 'enabled' if w['wireless'].get('hide_ssid') else 'disabled' encryption = w['wireless'].get('encryption', 'none') + status = 'disabled' if w['wireless'].get('disabled') else 'enabled' ws.append({ 'addmtikie': 'enabled', 'devname': w['name'], @@ -443,7 +444,7 @@ def to_intermediate(self): 'signal_led4': 15, 'signal_led_status': 'enabled', 'ssid': w['wireless']['ssid'], - 'status': 'enabled', + 'status': status, 'wds': { 'status': 'enabled', }, @@ -456,6 +457,7 @@ def to_intermediate(self): return (('wireless', result),) + class Wpasupplicant(BaseConverter): netjson_key = 'general' From 8c407462afdaa66e1427b0be9bfa82c3e376c202 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 9 Jun 2017 12:57:06 +0200 Subject: [PATCH 044/342] [airos] add defaults to converters --- netjsonconfig/backends/airos/converters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index e6aca5807..a07a5baf8 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -182,7 +182,7 @@ def to_intermediate(self): result = [] result.append({ - 'status': 'enabled', + 'status': 'disabled', }) return (('ntpclient',result),) @@ -218,6 +218,7 @@ def to_intermediate(self): 'devname': r['name'], 'status': 'enabled', 'txpower': r.get('tx_power', ''), + 'chanbw': r.get('channel_width', ''), }) result.append({ From 2194c5f729d43cf4c6a79836515e62e6f0c35a1d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 9 Jun 2017 12:58:56 +0200 Subject: [PATCH 045/342] [airos] fix whitespace in backend template --- netjsonconfig/backends/airos/templates/airos.jinja2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/templates/airos.jinja2 b/netjsonconfig/backends/airos/templates/airos.jinja2 index a50795b53..0ae7deccb 100644 --- a/netjsonconfig/backends/airos/templates/airos.jinja2 +++ b/netjsonconfig/backends/airos/templates/airos.jinja2 @@ -1,7 +1,7 @@ {% for namespace, block in data.items() %} {% for element in block %} {% for k,v in element.items() %} - {{ namespace }}.{{ k }} = {{ v }} + {{ namespace }}.{{ k }}={{ v }} {% endfor %} {% endfor %} {% endfor %} From 70944088346027634f6ad6830a9d2e028396a2e1 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 12 Jun 2017 11:39:56 +0200 Subject: [PATCH 046/342] [airos] fill netconf entry with interface addresses --- netjsonconfig/backends/airos/converters.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index a07a5baf8..c313e1200 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -151,12 +151,24 @@ def to_intermediate(self): for interface in original: - interfaces.append({ - 'devname': interface['name'], - 'status': 'disabled' if interface.get('disabled', False) else 'enabled', - }) + addresses = interface.get('addresses', []) + + for addr in addresses: + temp = { + 'devname': interface['name'], + 'status': status(interface), + 'up': status(interface), + 'mut': interface.get('mtu', 1500), + } + if addr['proto'] == 'dhcp': + temp['autoip']['status'] = 'enabled' + else: + temp['ip'] = addr['address'] + temp['netmask'] = addr['mask'] + + interfaces.append(temp) - result.append(interfaces) + result.apend(interfaces) result.append({ 'status': 'enabled', }) From 7b77fa00352e1c5f5a816de981caa84820ba7db3 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 12 Jun 2017 11:40:27 +0200 Subject: [PATCH 047/342] [airos] define helper function for status in converters --- netjsonconfig/backends/airos/converters.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index c313e1200..4752dc1b8 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -2,6 +2,13 @@ from ..base.converter import BaseConverter +def status(config, key='disabled'): + if config.get(key): + return 'disabled' + else: + return 'enabled' + + class Aaa(BaseConverter): netjson_key = 'general' @@ -28,6 +35,7 @@ def to_intermediate(self): ]) return (('aaa', result),) + class Bridge(BaseConverter): netjson_key = 'interfaces' @@ -51,7 +59,7 @@ def to_intermediate(self): 'comment': interface.get('comment', ''), 'devname': interface['name'], 'port': bridge_ports, - 'status': 'disabled' if interface['disabled'] else 'enabled', + 'status': status(interface), 'stp': { 'status': 'enabled', } @@ -196,8 +204,7 @@ def to_intermediate(self): result.append({ 'status': 'disabled', }) - return (('ntpclient',result),) - + return (('ntpclient', result),) class Pwdog(BaseConverter): @@ -419,7 +426,7 @@ def to_intermediate(self): 'comment': v.get('comment', ''), 'devname': v['name'].split('.')[0], 'id': v['name'].split('.')[1], - 'status': 'disabled' if v['disabled'] else 'enabled', + 'status': status(v), }) result.append(vlans) @@ -441,13 +448,11 @@ def to_intermediate(self): ws = [] for w in original: - hide_ssid = 'enabled' if w['wireless'].get('hide_ssid') else 'disabled' encryption = w['wireless'].get('encryption', 'none') - status = 'disabled' if w['wireless'].get('disabled') else 'enabled' ws.append({ 'addmtikie': 'enabled', 'devname': w['name'], - 'hide_ssid': hide_ssid, + 'hide_ssid': status(w['wireless'], 'hide_ssid'), 'security': { 'type': encryption }, @@ -457,7 +462,7 @@ def to_intermediate(self): 'signal_led4': 15, 'signal_led_status': 'enabled', 'ssid': w['wireless']['ssid'], - 'status': status, + 'status': status(w), 'wds': { 'status': 'enabled', }, From 1e3d2ddedb2550d154ac3e19a9802ce0c472a745 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 12 Jun 2017 13:54:25 +0200 Subject: [PATCH 048/342] [airos] fix broken logic in hide_ssid status --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 4752dc1b8..e827924b0 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -452,7 +452,7 @@ def to_intermediate(self): ws.append({ 'addmtikie': 'enabled', 'devname': w['name'], - 'hide_ssid': status(w['wireless'], 'hide_ssid'), + 'hide_ssid': 'enabled' if w['wireless'].get('hidden') else 'disabled', 'security': { 'type': encryption }, From 834de80af93327b5557b3c1e99e8cb38adcb7e47 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 12 Jun 2017 14:53:47 +0200 Subject: [PATCH 049/342] [airos] fixed aaa converter --- netjsonconfig/backends/airos/converters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index e827924b0..4df0dcf1c 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -15,6 +15,9 @@ class Aaa(BaseConverter): def to_intermediate(self): result = [] + result.append({ + 'status': 'disabled', + }) result.append([ { # 'radius': { @@ -30,7 +33,6 @@ def to_intermediate(self): # }, # ], # }, - 'status': 'disabled', } ]) return (('aaa', result),) From 557d8f360002b6ec41a911f6f9138e4fda44bf25 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 13 Jun 2017 09:42:49 +0200 Subject: [PATCH 050/342] [airos] remove empty settings from flattened structure --- netjsonconfig/backends/airos/airos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index b3b783529..8e28c063f 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -52,7 +52,7 @@ class AirOS(BaseBackend): def to_intermediate(self): super(AirOS, self).to_intermediate() for k,v in self.intermediate_data.items(): - self.intermediate_data[k] = flatten(intermediate_to_list(v)) + self.intermediate_data[k] = filter(lambda x: x != {}, flatten(intermediate_to_list(v))) def flatten(xs): """ From c56c97860eb8128a423a7b1e589f44a3396d4f9b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 13 Jun 2017 09:44:13 +0200 Subject: [PATCH 051/342] [airos] changed from list comprehension to simpler assignment --- netjsonconfig/backends/airos/converters.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 4df0dcf1c..133c41b0e 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -155,9 +155,7 @@ class Netconf(BaseConverter): def to_intermediate(self): result = [] interfaces = [] - original = [ - i for i in get_copy(self.netjson, self.netjson_key) - ] + original = get_copy(self.netjson, self.netjson_key) for interface in original: From 0023ccdafd42080da023ab332a1b7c1216b3b6df Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 13 Jun 2017 09:48:51 +0200 Subject: [PATCH 052/342] [airos] fixed typo in append, use status un radio converter --- netjsonconfig/backends/airos/converters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 133c41b0e..4a5e96682 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -176,7 +176,7 @@ def to_intermediate(self): interfaces.append(temp) - result.apend(interfaces) + result.append(interfaces) result.append({ 'status': 'enabled', }) @@ -235,11 +235,13 @@ def to_intermediate(self): for r in original: radios.append({ 'devname': r['name'], - 'status': 'enabled', + 'status': status(r), 'txpower': r.get('tx_power', ''), 'chanbw': r.get('channel_width', ''), }) + result.append(radios) + result.append({ 'status': 'enabled', }) From 922ec99ef61ad775bce45bb595f9e79326b464b1 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 21 Jun 2017 11:51:30 +0200 Subject: [PATCH 053/342] [airos] fixed problems with netmask representation and dhcp keyerror --- netjsonconfig/backends/airos/converters.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 4a5e96682..3c6969a29 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -1,6 +1,7 @@ from ...utils import get_copy, sorted_dict from ..base.converter import BaseConverter +from ipaddress import ip_interface def status(config, key='disabled'): if config.get(key): @@ -169,10 +170,12 @@ def to_intermediate(self): 'mut': interface.get('mtu', 1500), } if addr['proto'] == 'dhcp': + temp['autoip'] = {} temp['autoip']['status'] = 'enabled' else: - temp['ip'] = addr['address'] - temp['netmask'] = addr['mask'] + network = ip_interface('%s/%d' % (addr['address'],addr['mask'])) + temp['ip'] = str(network.ip) + temp['netmask'] = str(network.netmask) interfaces.append(temp) From 5941ea19409591a3704b39d4eab85e01c79ac7f5 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 21 Jun 2017 16:02:46 +0200 Subject: [PATCH 054/342] [airos] set interfaces as always enabled --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 3c6969a29..8b59f2001 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -165,7 +165,7 @@ def to_intermediate(self): for addr in addresses: temp = { 'devname': interface['name'], - 'status': status(interface), + 'status': 'enabled', # can't disable interfaces 'up': status(interface), 'mut': interface.get('mtu', 1500), } From 1093faaab4e1cb366b19a7bb04163c626c2dc41e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 21 Jun 2017 16:03:24 +0200 Subject: [PATCH 055/342] [airos] set interface name from phisical interface it turns out that in the netconf section it does not care about the logical interface but only about the phisical interface. Be ware that a vlan interface is still a phisical interface --- netjsonconfig/backends/airos/converters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 8b59f2001..84737c843 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -177,6 +177,9 @@ def to_intermediate(self): temp['ip'] = str(network.ip) temp['netmask'] = str(network.netmask) + if interface['type'] == 'wireless': + temp['devname'] = interface['wireless']['radio'] + interfaces.append(temp) result.append(interfaces) From 4d2d322f28fbef08ff56a2bdd48e8c5a61843166 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 22 Jun 2017 11:59:48 +0200 Subject: [PATCH 056/342] [airos] add base definition for interface --- netjsonconfig/backends/airos/converters.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 84737c843..125a1dfa6 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -159,6 +159,12 @@ def to_intermediate(self): original = get_copy(self.netjson, self.netjson_key) for interface in original: + base = { + 'devname': interface['name'], + 'status': 'enabled', # can't disable interfaces + 'up': status(interface), + 'mtu': interface.get('mtu', 1500), + } addresses = interface.get('addresses', []) From 88337bac4f551182126baf788ec8f70aff2524bd Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 22 Jun 2017 12:02:24 +0200 Subject: [PATCH 057/342] [airos] moved type bounded configuration after base interface --- netjsonconfig/backends/airos/converters.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 125a1dfa6..68d063c9c 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -166,6 +166,21 @@ def to_intermediate(self): 'mtu': interface.get('mtu', 1500), } + # handle interface type quirks + if interface['type'] == 'ethernet' and not '.' in interface['name']: + base['autoneg'] = 'enabled' + base['flowcontrol'] = { + 'rx': { + 'status': 'enabled', + }, + 'tx': { + 'status': 'enabled', + }, + } + + if interface['type'] == 'wireless': + base['devname'] = interface['wireless']['radio'] + addresses = interface.get('addresses', []) for addr in addresses: From 3c01a9a6003c34520f677ac647beaf7037de12cd Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 22 Jun 2017 12:05:59 +0200 Subject: [PATCH 058/342] [airos] check for interface without address definition --- netjsonconfig/backends/airos/converters.py | 47 ++++++++++++---------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 68d063c9c..439273fcf 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -181,27 +181,32 @@ def to_intermediate(self): if interface['type'] == 'wireless': base['devname'] = interface['wireless']['radio'] - addresses = interface.get('addresses', []) - - for addr in addresses: - temp = { - 'devname': interface['name'], - 'status': 'enabled', # can't disable interfaces - 'up': status(interface), - 'mut': interface.get('mtu', 1500), - } - if addr['proto'] == 'dhcp': - temp['autoip'] = {} - temp['autoip']['status'] = 'enabled' - else: - network = ip_interface('%s/%d' % (addr['address'],addr['mask'])) - temp['ip'] = str(network.ip) - temp['netmask'] = str(network.netmask) - - if interface['type'] == 'wireless': - temp['devname'] = interface['wireless']['radio'] - - interfaces.append(temp) + addresses = interface.get('addresses') + + if addresses: + # for every address policy put a + # configuration + for addr in addresses: + temp = deepcopy(base) + + # handle explicit address policy + if addr['proto'] == 'dhcp': + temp['autoip'] = {} + temp['autoip']['status'] = 'enabled' + else: + ip_and_mask = '%s/%d' % (addr['address'], addr['mask']) + network = ip_interface(ip_and_mask) + temp['ip'] = str(network.ip) + temp['netmask'] = str(network.netmask) + + interfaces.append(temp) + else: + # an interface without address + # is still valid with these defaults values + base['autoip'] = { + 'status': 'disabled', + } + interfaces.append(base) result.append(interfaces) result.append({ From 471c3a63a6fc2114f091f21c94e7e71ef54d62db Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 22 Jun 2017 12:06:40 +0200 Subject: [PATCH 059/342] import deepcopy in converters --- netjsonconfig/backends/airos/converters.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 439273fcf..4ee60615b 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -1,8 +1,10 @@ +from copy import deepcopy from ...utils import get_copy, sorted_dict from ..base.converter import BaseConverter from ipaddress import ip_interface + def status(config, key='disabled'): if config.get(key): return 'disabled' From 4469d793550192265c686e1dfc434dc26d5f46e1 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 26 Jun 2017 09:17:19 +0200 Subject: [PATCH 060/342] [airos][doc] move intermediate representation into own file --- docs/source/backends/airos.rst | 248 ------------------------- docs/source/backends/intermediate.rst | 254 ++++++++++++++++++++++++++ 2 files changed, 254 insertions(+), 248 deletions(-) create mode 100644 docs/source/backends/intermediate.rst diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index d034ba11d..ab4681ce9 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -29,251 +29,3 @@ read about the following basic concepts: * :ref:`configuration_dictionary` * :ref:`template` * :ref:`context` - -Intermediate representation ---------------------------- - -The intermediate representation is the output of the a :ref:`converter`, -it is backend specific and is built as a tree structure made from python -builtins values - -A tree is a *acyclic, directional graph* with an element called *root*. - -The root of our tree is stored in the first element of a tuple, along with -the root's direct sons as a list - -.. code-block:: python - - tree = (root, direct_sons) - -As an example here we present the tree `('spam', ['eggs', 'snakes'])` - -.. graphviz:: - - digraph tree { - spam -> {eggs, snakes}; - } - -As a son may be a carrier of a value so we store it in a dictionary instead of adding a *leaf* -with another level of recursion - -As an example here we present the tree `('spam', [ { 'eggs': 2 }, { 'snakes' : { 'loved' : 'python' }}])` - -.. graphviz:: - - digraph tree { - - eggs[label="{ eggs : 2 }"]; - loved[label="{ loved : python }"]; - - spam -> eggs; - spam -> snakes -> loved; - - } - -This tree could be tranlated to a configuration file for AirOS that looks like this - -.. code-block:: ini - - spam.eggs=2 - spam.snakes.loved=python - - -So our tree representation is based on the simple assumption that a *leaf* is a dictionary -without nested values and nested values in a dictionary creates a father-son relationship - -Instead when the configuration requires that the son values must be prefixed from a number, -e.g. `vlan.1.devname=eth0` we store a list of dictionaries. - -.. code-block:: python - - ( - 'spam', - [ - { - 'eggs' : 2, - }, - { - 'snakes' : { - 'loved' : [ - { - 'python2' : True, - }, - { - 'python3' : True, - }, - { - 'ipython' : True, - } - ], - }, - } - ] - -And the resulting tree is this - -.. graphviz:: - - digraph tree { - - eggs[label="{ eggs : 2 }"]; - loved; - - python2[label="{ python2 : True }"]; - python3[label="{ python3 : True }"]; - ipython[label="{ ipython : True }"]; - - spam -> eggs; - spam -> snakes -> loved; - - loved -> {1,2,3}; - - 1 -> python2; - 2 -> python3; - 3 -> ipython; - - } - -And the configuration is - -.. code-block:: ini - - spam.eggs=2 - spam.snakes.loved.1.python2=true - spam.snakes.loved.2.python3=true - spam.snakes.loved.2.ipython=true - -The process by which we can go from the intermediate representation from -the output configuration is called flattening - -Flattening ----------- - -To avoid at all cost a recursive logic in the template we flatten the intermediate -representation to something that has a *namespace* a *key* and a *value* - -This input NetJSON will be converted to a python :ref:`configuration_dictionary` - -.. code-block:: json - - //netjson - { - "type" : "DeviceConfiguration", - "interfaces" : [ - { - "name" : "eth0.1", - "type" : "ethernet", - "comment" : "management vlan" - }, - { - "name" : "eth0.2", - "type" : "ethernet", - "comment" : "traffic vlan" - } - ] - } - -.. code-block:: python - - #python - { - 'interfaces' : [ - { - 'name' : 'eth0.1', - 'type' : 'ethernet', - 'comment' : 'management' - }, - { - 'name' : 'eth0.2', - 'type' : 'ethernet', - 'comment' : 'traffic' - } - ] - } - - -And this must be converted to an appropiate AirOS configuration which looks like this - -.. code-block:: ini - - vlan.1.comment=management - vlan.1.devname=eth0 - vlan.1.id=1 - vlan.1.status=enabled - vlan.2.comment=management - vlan.2.devname=eth0 - vlan.2.id=2 - vlan.2.status=enabled - vlan.status=enabled - -To do this we must convert the :ref:`configuration_dictionary` into something that -resemble the target text, the output configuration - -.. code-block:: python - - ( - # namespace - 'vlan', - #options - [ - { - # key : value - '1.devname' : 'eth0', - '1.id' : '1' - '1.status' : 'enabled', - '1.comment' : 'management' - }, - { - '2.devname' : 'eth0', - '2.id' : '2' - '2.status' : 'enabled', - '2.comment' : 'traffic' - } - ] - - ) - -And to do that we get rid of the multiple indentation levels by flattening the tree structure - -The tree associated with the previous NetJSON example is this - -.. graphviz:: - - digraph tree { - vlan -> {1,2}; - devname1 [label="devname=eth0"]; - devname2 [label="devname=eth0"]; - - id1 [label="id=1"]; - id2 [label="id=2"]; - - status1 [label="status=enabled"]; - status2 [label="status=enabled"]; - - comment1 [label="comment=management"]; - comment2 [label="comment=traffic"]; - - 1 -> {devname1, id1, status1, comment1}; - 2 -> {devname2, id2, status2, comment2}; - } - -And by exploring depth first we get to read a line of the configuration at a time. - -E.g. following the blue line from the `vlan` root to the first `leaf` we have the -configuration `vlan.1.devname=eth0` - -.. graphviz:: - - digraph tree { - vlan -> 1 [color="blue"]; - devname1 [label="devname=eth0"]; - - id1 [label="id=1"]; - - status1 [label="status=enabled"]; - - comment1 [label="comment=management"]; - - 1 -> devname1 [color="blue"]; - 1 -> {id1, status1, comment1}; - } diff --git a/docs/source/backends/intermediate.rst b/docs/source/backends/intermediate.rst new file mode 100644 index 000000000..507c80ec6 --- /dev/null +++ b/docs/source/backends/intermediate.rst @@ -0,0 +1,254 @@ +============= +AirOS Backend +============= + +.. include:: ../_github.rst + + +Intermediate representation +--------------------------- + +The intermediate representation is the output of the a :ref:`converter`, +it is backend specific and is built as a tree structure made from python +builtins values + +A tree is a *acyclic, directional graph* with an element called *root*. + +The root of our tree is stored in the first element of a tuple, along with +the root's direct sons as a list + +.. code-block:: python + + tree = (root, direct_sons) + +As an example here we present the tree `('spam', ['eggs', 'snakes'])` + +.. graphviz:: + + digraph tree { + spam -> {eggs, snakes}; + } + +As a son may be a carrier of a value so we store it in a dictionary instead of adding a *leaf* +with another level of recursion + +As an example here we present the tree `('spam', [ { 'eggs': 2 }, { 'snakes' : { 'loved' : 'python' }}])` + +.. graphviz:: + + digraph tree { + + eggs[label="{ eggs : 2 }"]; + loved[label="{ loved : python }"]; + + spam -> eggs; + spam -> snakes -> loved; + + } + +This tree could be tranlated to a configuration file for AirOS that looks like this + +.. code-block:: ini + + spam.eggs=2 + spam.snakes.loved=python + + +So our tree representation is based on the simple assumption that a *leaf* is a dictionary +without nested values and nested values in a dictionary creates a father-son relationship + +Instead when the configuration requires that the son values must be prefixed from a number, +e.g. `vlan.1.devname=eth0` we store a list of dictionaries. + +.. code-block:: python + + ( + 'spam', + [ + { + 'eggs' : 2, + }, + { + 'snakes' : { + 'loved' : [ + { + 'python2' : True, + }, + { + 'python3' : True, + }, + { + 'ipython' : True, + } + ], + }, + } + ] + +And the resulting tree is this + +.. graphviz:: + + digraph tree { + + eggs[label="{ eggs : 2 }"]; + loved; + + python2[label="{ python2 : True }"]; + python3[label="{ python3 : True }"]; + ipython[label="{ ipython : True }"]; + + spam -> eggs; + spam -> snakes -> loved; + + loved -> {1,2,3}; + + 1 -> python2; + 2 -> python3; + 3 -> ipython; + + } + +And the configuration is + +.. code-block:: ini + + spam.eggs=2 + spam.snakes.loved.1.python2=true + spam.snakes.loved.2.python3=true + spam.snakes.loved.2.ipython=true + +The process by which we can go from the intermediate representation from +the output configuration is called flattening + +Flattening +---------- + +To avoid at all cost a recursive logic in the template we flatten the intermediate +representation to something that has a *namespace* a *key* and a *value* + +This input NetJSON will be converted to a python :ref:`configuration_dictionary` + +.. code-block:: json + + //netjson + { + "type" : "DeviceConfiguration", + "interfaces" : [ + { + "name" : "eth0.1", + "type" : "ethernet", + "comment" : "management vlan" + }, + { + "name" : "eth0.2", + "type" : "ethernet", + "comment" : "traffic vlan" + } + ] + } + +.. code-block:: python + + #python + { + 'interfaces' : [ + { + 'name' : 'eth0.1', + 'type' : 'ethernet', + 'comment' : 'management' + }, + { + 'name' : 'eth0.2', + 'type' : 'ethernet', + 'comment' : 'traffic' + } + ] + } + + +And this must be converted to an appropiate AirOS configuration which looks like this + +.. code-block:: ini + + vlan.1.comment=management + vlan.1.devname=eth0 + vlan.1.id=1 + vlan.1.status=enabled + vlan.2.comment=management + vlan.2.devname=eth0 + vlan.2.id=2 + vlan.2.status=enabled + vlan.status=enabled + +To do this we must convert the :ref:`configuration_dictionary` into something that +resemble the target text, the output configuration + +.. code-block:: python + + ( + # namespace + 'vlan', + #options + [ + { + # key : value + '1.devname' : 'eth0', + '1.id' : '1' + '1.status' : 'enabled', + '1.comment' : 'management' + }, + { + '2.devname' : 'eth0', + '2.id' : '2' + '2.status' : 'enabled', + '2.comment' : 'traffic' + } + ] + + ) + +And to do that we get rid of the multiple indentation levels by flattening the tree structure + +The tree associated with the previous NetJSON example is this + +.. graphviz:: + + digraph tree { + vlan -> {1,2}; + devname1 [label="devname=eth0"]; + devname2 [label="devname=eth0"]; + + id1 [label="id=1"]; + id2 [label="id=2"]; + + status1 [label="status=enabled"]; + status2 [label="status=enabled"]; + + comment1 [label="comment=management"]; + comment2 [label="comment=traffic"]; + + 1 -> {devname1, id1, status1, comment1}; + 2 -> {devname2, id2, status2, comment2}; + } + +And by exploring depth first we get to read a line of the configuration at a time. + +E.g. following the blue line from the `vlan` root to the first `leaf` we have the +configuration `vlan.1.devname=eth0` + +.. graphviz:: + + digraph tree { + vlan -> 1 [color="blue"]; + devname1 [label="devname=eth0"]; + + id1 [label="id=1"]; + + status1 [label="status=enabled"]; + + comment1 [label="comment=management"]; + + 1 -> devname1 [color="blue"]; + 1 -> {id1, status1, comment1}; + } From ba29a70b95367b9063299f8045958ee2dbfdab77 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 26 Jun 2017 10:17:30 +0200 Subject: [PATCH 061/342] [docs] add method documentation for backen --- docs/source/backends/airos.rst | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index ab4681ce9..a5cd58029 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -4,7 +4,7 @@ AirOS Backend .. include:: ../_github.rst -The ``AirOS`` backend allows to generate AirOS compatible configurations. +The ``AirOS`` backend allows to generate AirOS v8.3 compatible configurations. Initialization -------------- @@ -29,3 +29,25 @@ read about the following basic concepts: * :ref:`configuration_dictionary` * :ref:`template` * :ref:`context` + +Render method +------------- + +.. automethod:: netjsonconfig.AirOS.render + +Generate method +--------------- + +.. automethod:: netjsonconfig.AirOS.generate + + +Write method +------------ + +.. automethod:: netjsonconfig.AirOS.write + + +JSON method +----------- + +.. automethod:: netjsonconfig.AirOS.json From 97f5748205ffda9a9a5c27f56e9460b2956ecec2 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 26 Jun 2017 10:18:40 +0200 Subject: [PATCH 062/342] [docs] draft for netjson keys supported by airos backend --- docs/source/backends/airos.rst | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index a5cd58029..e2591aa67 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -51,3 +51,47 @@ JSON method ----------- .. automethod:: netjsonconfig.AirOS.json + + +General settings +---------------- + +Network interface +----------------- + +From the ``interfaces`` key we can configure the device network interfaces. + +AirOS supports the following types of interfaces + +* **network interfaces**: may be of type ``ethernet`` +* **wirelesss interfaces**: must be of type ``wireless`` +* **bridge interfaces**: must be of type ``bridge`` + +A network interface can be designed to be the management interfaces by setting the ``managed`` key to ``True`` on the address chosen. + +As an example here is a snippet that set the vlan ``eth0.2`` to be the management interface on the address ``192.168.1.20`` + +.. code-block:: json + + { + "interfaces": [ + { + "name": "eth0.2", + "type": "ethernet", + "addresses": [ + { + "address": "192.168.1.20", + "family": "ipv4", + "managed": true, + "mask": 24, + "proto": "static" + } + ] + } + ] + } + +DNS servers +----------- + + From 4ee470ba535f8566532b92890caac14374f4ad20 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 28 Jun 2017 15:56:10 +0200 Subject: [PATCH 063/342] [airos] add ebtables converter --- netjsonconfig/backends/airos/converters.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 4ee60615b..10c75a74c 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -110,6 +110,25 @@ def to_intermediate(self): return (('dyndns', result),) +class Ebtables(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [ + { + 'sys': { + 'fw': { + 'status': 'enabled', + }, + 'status': 'enabled', + }, + 'status': 'enabled', + }, + ] + + return (('httpd', result),) + + class Gui(BaseConverter): netjson_key = 'gui' From a8a2e7989985843e419a54fe27a7217ffd74ce9d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 28 Jun 2017 15:56:44 +0200 Subject: [PATCH 064/342] [airos] add igmpproxy converter --- netjsonconfig/backends/airos/converters.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 10c75a74c..1fab64ebe 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -171,6 +171,19 @@ def to_intermediate(self): return (('httpd', result),) +class Igmpproxy(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [ + { + 'status': 'enabled', + }, + ] + + return (('igmpproxy', result),) + + class Netconf(BaseConverter): netjson_key = 'interfaces' From 3a19cc9494fccff9bf4cf95c190346b252ad210c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 28 Jun 2017 15:58:26 +0200 Subject: [PATCH 065/342] [airos] add iptables converter --- netjsonconfig/backends/airos/converters.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 1fab64ebe..767a1bf61 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -184,6 +184,25 @@ def to_intermediate(self): return (('igmpproxy', result),) +class Iptables(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + result = [ + { + 'sys': { + 'portfw': { + 'status': 'enabled', + }, + 'status': 'enabled', + }, + 'status': 'enabled', + }, + ] + + return (('iptables', result),) + + class Netconf(BaseConverter): netjson_key = 'interfaces' From 6d8592a5e0d9087e31c64f8744a1488adb9f2213 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 28 Jun 2017 15:58:56 +0200 Subject: [PATCH 066/342] [airos] add tshaper converter --- netjsonconfig/backends/airos/converters.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 767a1bf61..0f78ce43f 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -463,6 +463,14 @@ def to_intermediate(self): return (('telnetd', result),) +class Tshaper(BaseConverter): + netjson_key = 'general' + + def to_intermediate(self): + + return (('tshaper', [{'status': 'disabled', }]),) + + class Update(BaseConverter): netjson_key = 'general' From 3168a3016954cc15142cf96ede67f29f9c0ea2b3 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 28 Jun 2017 15:59:18 +0200 Subject: [PATCH 067/342] [airos] add unms converter --- netjsonconfig/backends/airos/converters.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 0f78ce43f..4e1c7080e 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -471,6 +471,14 @@ def to_intermediate(self): return (('tshaper', [{'status': 'disabled', }]),) +class Unms(BaseConverter): + netjson_keu = 'general' + + def to_intermediate(self): + + return (('unms', [{'status': 'disabled'}]),) + + class Update(BaseConverter): netjson_key = 'general' From 31f38cb1e53a0c5d033350bb7e27c4676806cc78 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 28 Jun 2017 16:23:43 +0200 Subject: [PATCH 068/342] [airos] get hostname from configuration in resolv converter --- netjsonconfig/backends/airos/converters.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 4e1c7080e..38bbaae93 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -340,9 +340,13 @@ class Resolv(BaseConverter): def to_intermediate(self): result = [] + original = get_copy(self.netjson, "general") + hostname = original.get('hostname', 'airos') + result.append({ 'host': [{ - 'name': '' + 'name': hostname, + 'status': 'enabled', }] }) From 9095b8dd43f6a8a914d019935b078ef0406f5b8b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 28 Jun 2017 16:33:46 +0200 Subject: [PATCH 069/342] [airos] update system converter --- netjsonconfig/backends/airos/converters.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 38bbaae93..8bf06c658 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -447,9 +447,21 @@ def to_intermediate(self): result = [] result.append({ + 'airosx': { + 'prov': { + 'status': 'enabled', + }, + }, + 'cfg': { + 'version': 0, + }, + 'date': { + 'status': 'enabled', + }, 'external': { 'reset': 'enabled', }, + 'timezone': 'GMT', }) return (('system', result),) From a09a5483f577d44beca15a9cbbf2a4978c9f9d29 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 29 Jun 2017 15:43:22 +0200 Subject: [PATCH 070/342] [airos] add role to netconf interface, fix wireless name --- netjsonconfig/backends/airos/converters.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 8bf06c658..a1ad88596 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -206,6 +206,13 @@ def to_intermediate(self): class Netconf(BaseConverter): netjson_key = 'interfaces' + def type_to_role(self, typestr): + roles = { + 'ethernet': 'mlan', + 'bridge': 'mlan', + } + return roles.get(typestr,'') + def to_intermediate(self): result = [] interfaces = [] @@ -570,7 +577,7 @@ def to_intermediate(self): encryption = w['wireless'].get('encryption', 'none') ws.append({ 'addmtikie': 'enabled', - 'devname': w['name'], + 'devname': w['wireless']['radio'], 'hide_ssid': 'enabled' if w['wireless'].get('hidden') else 'disabled', 'security': { 'type': encryption From d73eb4ff8a235c5b98edcb3973c2bb821e7815d7 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 29 Jun 2017 16:08:39 +0200 Subject: [PATCH 071/342] [airos] set default values for aaa converters --- netjsonconfig/backends/airos/converters.py | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index a1ad88596..8cf591f3a 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -23,19 +23,19 @@ def to_intermediate(self): }) result.append([ { -# 'radius': { -# 'acct': [ -# { -# 'port': 1813, -# 'status': 'disabled', -# }, -# ], -# 'auth': [ -# { -# 'port': 1812, -# }, -# ], -# }, + 'radius': { + 'acct': [ + { + 'port': 1813, + 'status': 'disabled', + }, + ], + 'auth': [ + { + 'port': 1812, + }, + ], + }, } ]) return (('aaa', result),) From 92dee1247224a2947d09243d8375d53bf3a5acb2 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 3 Jul 2017 11:55:17 +0200 Subject: [PATCH 072/342] [airos] add defaults for wpasupplicant converter --- netjsonconfig/backends/airos/converters.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 8cf591f3a..1256c7894 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -611,17 +611,37 @@ def to_intermediate(self): result.append({ 'device': [ { + 'profile': 'AUTO', 'status': 'disabled', }, ], 'profile': [ { + 'name': 'AUTO', 'network': [ { + 'key_mgmt': [ + { + 'name': 'NONE', + }, + ], + 'priority': 100, 'ssid': 'your-ssid-here', }, + { + 'key_mgmt': [ + { + 'name': 'NONE', + }, + ], + 'priority': 2, + 'status': 'disabled', + }, ], }, ], }) + result.append({ + 'status': 'enabled', + }) return (('wpasupplicant', result),) From 66f563d924aac609b4c995b2d623c14d18f78181 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 3 Jul 2017 16:19:09 +0200 Subject: [PATCH 073/342] remove encryption setting from wireless converter --- netjsonconfig/backends/airos/converters.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 1256c7894..299a01da8 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -574,13 +574,12 @@ def to_intermediate(self): ws = [] for w in original: - encryption = w['wireless'].get('encryption', 'none') ws.append({ 'addmtikie': 'enabled', 'devname': w['wireless']['radio'], 'hide_ssid': 'enabled' if w['wireless'].get('hidden') else 'disabled', 'security': { - 'type': encryption + 'type': 'none', }, 'signal_led1': 75, 'signal_led2': 50, From 5e0dad42ef403f3acf3687d9cca0be4ba02307e7 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 3 Jul 2017 16:23:38 +0200 Subject: [PATCH 074/342] generate wpassupplicant configuration from interface key --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 299a01da8..6d302df03 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -602,7 +602,7 @@ def to_intermediate(self): class Wpasupplicant(BaseConverter): - netjson_key = 'general' + netjson_key = 'interfaces' def to_intermediate(self): result = [] From 813a784c34473a2cc728ecf64b7b44b43b399964 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 3 Jul 2017 16:35:13 +0200 Subject: [PATCH 075/342] write configuration for wpasupplicant from network interface --- netjsonconfig/backends/airos/converters.py | 43 +++++++++++++++------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 6d302df03..fad763f89 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -607,26 +607,42 @@ class Wpasupplicant(BaseConverter): def to_intermediate(self): result = [] + original = [ + i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'wireless' + ] + + temp_dev = { + 'profile': 'AUTO', + 'status': 'enabled', + 'driver': 'madwifi', + 'devname': '', + } + + if original: + head = original[0] + temp_dev['devname'] = head['wireless']['radio'] + + if head['encryption']['protocol'] == 'wpa2_personal': + network = wpa2_personal(head) + + elif head['encryption']['protocol'] == 'wpa2_enterprise': + network = wpa2_enterprise(head) + + else: + network = no_encryption(head) + temp_dev['status'] = 'disabled' + del temp_dev['driver'] + del temp_dev['devname'] + result.append({ 'device': [ - { - 'profile': 'AUTO', - 'status': 'disabled', - }, + temp_dev, ], 'profile': [ { 'name': 'AUTO', 'network': [ - { - 'key_mgmt': [ - { - 'name': 'NONE', - }, - ], - 'priority': 100, - 'ssid': 'your-ssid-here', - }, + network, { 'key_mgmt': [ { @@ -643,4 +659,5 @@ def to_intermediate(self): result.append({ 'status': 'enabled', }) + return (('wpasupplicant', result),) From 1dea881a006ca44599fe0a2f7b3132796c9887ff Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 3 Jul 2017 16:35:53 +0200 Subject: [PATCH 076/342] add functions for network configuration in wpasupplicant --- netjsonconfig/backends/airos/converters.py | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index fad763f89..8884ffc40 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -601,6 +601,93 @@ def to_intermediate(self): return (('wireless', result),) +def no_encryption(interface): + """ + Returns the wpasupplicant.profile.1.network + for encryption None as the intermediate dict + """ + return { + 'phase2=auth': 'MSCHAPV2', + 'ssid': interface['wireless']['ssid'], + 'priority': 100, + 'key_mgmt': [ + { + 'name': 'NONE', + }, + ], + } + + +def wpa2_personal(interface): + """ + Returns the wpasupplicant.profile.1.network + for wpa2_personal as the indernediate dict + """ + return { + 'phase2=auth': 'MSCHAPV2', + 'eap': [ + { + 'status': 'disabled', + }, + ], + 'psk': interface['encryption']['key'], + 'pairwise': [ + { + 'name': 'CCMP', + }, + ], + 'proto': [ + { + 'name': 'RSN', + }, + ], + 'ssid': interface['wireless']['ssid'], + 'priority': 100, + 'key_mgmt': [ + { + 'name': 'WPA-PSK', + }, + ], + } + + +def wpa2_enterprise(interface): + """ + Returns the wpasupplicant.profile.1.network + for wpa2_enterprise as the intermediate dict + """ + return { + 'phase2=auth': 'MSCHAPV2', + 'eap': [ + { + 'name': 'TTLS', + 'status': 'enabled', + }, + ], + 'password': 'TODO', + 'identity': 'TODO', + 'anonymous_identity': 'TODO', + 'psk': interface['encryption']['key'], + 'pairwise': [ + { + 'name': 'CCMP', + }, + ], + 'proto': [ + { + 'name': 'RSN', + }, + ], + 'ssid': interface['wireless']['ssid'], + 'priority': 100, + 'key_mgmt': [ + { + 'name': 'WPA-EAP', + }, + ], + } + + class Wpasupplicant(BaseConverter): netjson_key = 'interfaces' From 4772fd63212c1fd7de2ae6f7ad684d282e36a13a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 4 Jul 2017 10:45:28 +0200 Subject: [PATCH 077/342] [doc] add section for wpasupplicant configuration wpasupplicant is the section related to authentication protocols for wireless interfaces. AirOS v8.3 support both None, wpa2 personal and wpa2 enterprise --- docs/source/backends/airos.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index e2591aa67..d5825f306 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -95,3 +95,25 @@ DNS servers ----------- +WPA2 +---- + +AirOS v8.3 supports both WPA2 personal (PSK+CCMP) and WPA2 enterprise (EAP+CCMP) as an authentication protocol + +As an example here is a snippet that set the authentication protocol to WPA2 personal + +.. code-block:: json + + { + "interfaces": [ + { + "name": "wlan0", + "type": "wireless", + "encryption": { + "protocol": "wpa2_personal", + "key": "changeme" + } + } + ] + } + From 5f35ea4ac98de73b5fd7cfdc7a53ab9173fafd30 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 4 Jul 2017 11:02:29 +0200 Subject: [PATCH 078/342] [doc][wpasupplicant] add available ciphers to docs, add example --- docs/source/backends/airos.rst | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index d5825f306..45e4b080b 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -98,7 +98,7 @@ DNS servers WPA2 ---- -AirOS v8.3 supports both WPA2 personal (PSK+CCMP) and WPA2 enterprise (EAP+CCMP) as an authentication protocol +AirOS v8.3 supports both WPA2 personal (PSK+CCMP) and WPA2 enterprise (EAP+CCMP) as an authentication protocol. The only ciphers available is CCMP. As an example here is a snippet that set the authentication protocol to WPA2 personal @@ -117,3 +117,21 @@ As an example here is a snippet that set the authentication protocol to WPA2 per ] } +And another that set the authentication protocol to WPA2 enterprise + +.. code-block:: json + + { + "interfaces": [ + { + "name": "wlan0", + "type": "wireless", + "encryption": { + "protocol": "wpa2_enterprise", + "key": "changeme" + } + } + ] + } + +Leaving the `NetJSON Encryption object ` empty defaults to no encryption at all From d8c2e9d0eb3a21d07213735712a39739263845dd Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 4 Jul 2017 11:16:27 +0200 Subject: [PATCH 079/342] [airos] move wpasupplicant network generators in submodule --- netjsonconfig/backends/airos/converters.py | 95 +------------------ netjsonconfig/backends/airos/wpasupplicant.py | 91 ++++++++++++++++++ 2 files changed, 94 insertions(+), 92 deletions(-) create mode 100644 netjsonconfig/backends/airos/wpasupplicant.py diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 8884ffc40..4472baf8c 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,6 +4,7 @@ from ipaddress import ip_interface +from wpasupplicant import available_encryption_protocols def status(config, key='disabled'): if config.get(key): @@ -601,93 +602,6 @@ def to_intermediate(self): return (('wireless', result),) -def no_encryption(interface): - """ - Returns the wpasupplicant.profile.1.network - for encryption None as the intermediate dict - """ - return { - 'phase2=auth': 'MSCHAPV2', - 'ssid': interface['wireless']['ssid'], - 'priority': 100, - 'key_mgmt': [ - { - 'name': 'NONE', - }, - ], - } - - -def wpa2_personal(interface): - """ - Returns the wpasupplicant.profile.1.network - for wpa2_personal as the indernediate dict - """ - return { - 'phase2=auth': 'MSCHAPV2', - 'eap': [ - { - 'status': 'disabled', - }, - ], - 'psk': interface['encryption']['key'], - 'pairwise': [ - { - 'name': 'CCMP', - }, - ], - 'proto': [ - { - 'name': 'RSN', - }, - ], - 'ssid': interface['wireless']['ssid'], - 'priority': 100, - 'key_mgmt': [ - { - 'name': 'WPA-PSK', - }, - ], - } - - -def wpa2_enterprise(interface): - """ - Returns the wpasupplicant.profile.1.network - for wpa2_enterprise as the intermediate dict - """ - return { - 'phase2=auth': 'MSCHAPV2', - 'eap': [ - { - 'name': 'TTLS', - 'status': 'enabled', - }, - ], - 'password': 'TODO', - 'identity': 'TODO', - 'anonymous_identity': 'TODO', - 'psk': interface['encryption']['key'], - 'pairwise': [ - { - 'name': 'CCMP', - }, - ], - 'proto': [ - { - 'name': 'RSN', - }, - ], - 'ssid': interface['wireless']['ssid'], - 'priority': 100, - 'key_mgmt': [ - { - 'name': 'WPA-EAP', - }, - ], - } - - class Wpasupplicant(BaseConverter): netjson_key = 'interfaces' @@ -709,11 +623,8 @@ def to_intermediate(self): head = original[0] temp_dev['devname'] = head['wireless']['radio'] - if head['encryption']['protocol'] == 'wpa2_personal': - network = wpa2_personal(head) - - elif head['encryption']['protocol'] == 'wpa2_enterprise': - network = wpa2_enterprise(head) + if head['encryption']: + network = available_encryption_protocols.get(head['encryption']['protocol'])(head) else: network = no_encryption(head) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py new file mode 100644 index 000000000..b91f5d7ae --- /dev/null +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -0,0 +1,91 @@ +def no_encryption(interface): + """ + Returns the wpasupplicant.profile.1.network + for encryption None as the intermediate dict + """ + return { + 'phase2=auth': 'MSCHAPV2', + 'ssid': interface['wireless']['ssid'], + 'priority': 100, + 'key_mgmt': [ + { + 'name': 'NONE', + }, + ], + } + + +def wpa2_personal(interface): + """ + Returns the wpasupplicant.profile.1.network + for wpa2_personal as the indernediate dict + """ + return { + 'phase2=auth': 'MSCHAPV2', + 'eap': [ + { + 'status': 'disabled', + }, + ], + 'psk': interface['encryption']['key'], + 'pairwise': [ + { + 'name': 'CCMP', + }, + ], + 'proto': [ + { + 'name': 'RSN', + }, + ], + 'ssid': interface['wireless']['ssid'], + 'priority': 100, + 'key_mgmt': [ + { + 'name': 'WPA-PSK', + }, + ], + } + + +def wpa2_enterprise(interface): + """ + Returns the wpasupplicant.profile.1.network + for wpa2_enterprise as the intermediate dict + """ + return { + 'phase2=auth': 'MSCHAPV2', + 'eap': [ + { + 'name': 'TTLS', + 'status': 'enabled', + }, + ], + 'password': 'TODO', + 'identity': 'TODO', + 'anonymous_identity': 'TODO', + 'psk': interface['encryption']['key'], + 'pairwise': [ + { + 'name': 'CCMP', + }, + ], + 'proto': [ + { + 'name': 'RSN', + }, + ], + 'ssid': interface['wireless']['ssid'], + 'priority': 100, + 'key_mgmt': [ + { + 'name': 'WPA-EAP', + }, + ], + } + +available_encryption_protocols = { + 'none': no_encryption, + 'wpa2_personal': wpa2_personal, + 'wpa2_enterprise': wpa2_enterprise, +} From 89261dee6ec6e162803d2b2003131f2e1a0de0f5 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 4 Jul 2017 11:27:23 +0200 Subject: [PATCH 080/342] [doc] specify limits of wireless interface for device --- docs/source/backends/airos.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 45e4b080b..96a754a00 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -100,6 +100,8 @@ WPA2 AirOS v8.3 supports both WPA2 personal (PSK+CCMP) and WPA2 enterprise (EAP+CCMP) as an authentication protocol. The only ciphers available is CCMP. +As an antenna only has one wireless network available only the first wireless interface will be used during the generation. + As an example here is a snippet that set the authentication protocol to WPA2 personal .. code-block:: json From 85b33a580f432e91cd59d2c46fcbb36a4c8695e6 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 4 Jul 2017 11:28:30 +0200 Subject: [PATCH 081/342] [airos] add schema for wireless encryption --- netjsonconfig/backends/airos/converters.py | 2 +- netjsonconfig/backends/airos/schema.py | 40 +++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 4472baf8c..69e634396 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -627,7 +627,7 @@ def to_intermediate(self): network = available_encryption_protocols.get(head['encryption']['protocol'])(head) else: - network = no_encryption(head) + network = available_encryption_protocols['none'](head) temp_dev['status'] = 'disabled' del temp_dev['driver'] del temp_dev['devname'] diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index e8133b70c..e479e6679 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -5,4 +5,42 @@ from ...schema import DEFAULT_FILE_MODE # noqa - backward compatibility from ...utils import merge_config -schema = merge_config(default_schema, {}) +""" +This schema override the possible encryption for AirOS from the default schema +""" +wpasupplicant_schema = { + "encryption_wireless_property_sta": { + "properties": { + "encryption": { + "type": "object", + "title": "Encryption", + "required": "protocol", + "propertyOrder": 20, + "oneOf": [ + {"$ref": "#/definitions/encryption_none"}, + {"$ref": "#/definitions/encryption_wpa_personal"}, + {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, + ], + }, + }, + }, + "encryption_wireless_property_ap": { + "properties": { + "encryption": { + "type": "object", + "title": "Encryption", + "required": "protocol", + "propertyOrder": 20, + "oneOf": [ + {"$ref": "#/definitions/encryption_none"}, + {"$ref": "#/definitions/encryption_wpa_personal"}, + {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, + ], + }, + }, + }, +} + + + +schema = merge_config(default_schema, wpasupplicant_schema) From ac31282e128611c65490142d7809d20abc5fad00 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 5 Jul 2017 16:48:19 +0200 Subject: [PATCH 082/342] [airos][converter][wpasupplicant] fix check for encryption object This check is the correct one as the encryption object is the value for the key "encryption" in an interface --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 69e634396..65fce417a 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -623,7 +623,7 @@ def to_intermediate(self): head = original[0] temp_dev['devname'] = head['wireless']['radio'] - if head['encryption']: + if 'encryption' in head: network = available_encryption_protocols.get(head['encryption']['protocol'])(head) else: From ed856ec71423f7eba56b1d1a75df6423599fe4ef Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 5 Jul 2017 16:50:09 +0200 Subject: [PATCH 083/342] [wpasupplicant] fix incorrect values for intermediate configuration --- netjsonconfig/backends/airos/converters.py | 1 - netjsonconfig/backends/airos/wpasupplicant.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 65fce417a..5795f142c 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -628,7 +628,6 @@ def to_intermediate(self): else: network = available_encryption_protocols['none'](head) - temp_dev['status'] = 'disabled' del temp_dev['driver'] del temp_dev['devname'] diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index b91f5d7ae..4ce95f4d2 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -4,7 +4,6 @@ def no_encryption(interface): for encryption None as the intermediate dict """ return { - 'phase2=auth': 'MSCHAPV2', 'ssid': interface['wireless']['ssid'], 'priority': 100, 'key_mgmt': [ @@ -64,7 +63,6 @@ def wpa2_enterprise(interface): 'password': 'TODO', 'identity': 'TODO', 'anonymous_identity': 'TODO', - 'psk': interface['encryption']['key'], 'pairwise': [ { 'name': 'CCMP', From a9d3ead399132ceef98894728e2543889679b11b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 5 Jul 2017 17:27:44 +0200 Subject: [PATCH 084/342] =?UTF-8?q?[airos][aaa]=C2=A0add=20wpa2=5Fpersonal?= =?UTF-8?q?=20authentication=20password=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netjsonconfig/backends/airos/converters.py | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 5795f142c..e577f5369 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -16,6 +16,29 @@ def status(config, key='disabled'): class Aaa(BaseConverter): netjson_key = 'general' + def wpa2_personal(self): + """ + When using wpa_personal the wifi password is written + in ``aaa.1.wpa.psk`` instead of ``wpasupplicant`` + """ + wireless = [ i for i in get_copy(self.netjson, 'interfaces') if i['type'] == 'wireless'] + + def get_psk(interface): + t = { + 'wpa': { + 'psk': interface['encryption']['key'], + }, + } + return t + + def is_wpa2_personal(interface): + return interface['encryption']['protocol'] == 'wpa2_personal' + + try: + return [ get_psk(i) for i in wireless if is_wpa2_personal(i)][0] + except IndexError: + return {} + def to_intermediate(self): result = [] @@ -39,6 +62,11 @@ def to_intermediate(self): }, } ]) + + w = self.wpa2_personal() + if w: + result.append([w]) + return (('aaa', result),) From a92a432e31572331fbc6705f3361875a72dba032 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 7 Jul 2017 10:08:51 +0200 Subject: [PATCH 085/342] [airos][resolv] split converter in more function --- netjsonconfig/backends/airos/converters.py | 35 ++++++++++++---------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index e577f5369..3d365285e 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -373,31 +373,36 @@ def to_intermediate(self): class Resolv(BaseConverter): netjson_key = 'dns_servers' + @property + def hostname(self): + original = get_copy(self.netjson, 'general', {}) + return original.get('hostname', 'airos') + + def nameserver(self): + original = get_copy(self.netjson, self.netjson_key) + + t = [] + + for nameserver in original: + t.append({ + 'ip': nameserver, + 'status': 'enabled', + }) + + return { 'nameserver': t } + def to_intermediate(self): result = [] - original = get_copy(self.netjson, "general") - hostname = original.get('hostname', 'airos') - result.append({ 'host': [{ - 'name': hostname, + 'name': self.hostname, 'status': 'enabled', }] }) - original = get_copy(self.netjson, self.netjson_key) - - a = { - 'nameserver': [], - } - for nameserver in original: - a['nameserver'].append({ - 'ip': nameserver, - 'status': 'enabled', - }) - result.append(a) + result.append(self.nameserver()) result.append({ 'status': 'enabled', From c2d2639da01e96a140f54c0477d2ef60caa72532 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 7 Jul 2017 10:25:16 +0200 Subject: [PATCH 086/342] [resolv] moved host section into own function --- netjsonconfig/backends/airos/converters.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 3d365285e..9830b1b21 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -373,10 +373,14 @@ def to_intermediate(self): class Resolv(BaseConverter): netjson_key = 'dns_servers' - @property - def hostname(self): + def host(self): original = get_copy(self.netjson, 'general', {}) - return original.get('hostname', 'airos') + return { + 'host': [{ + 'name': original.get('hostname', 'airos'), + 'status': 'enabled', + }], + } def nameserver(self): original = get_copy(self.netjson, self.netjson_key) @@ -394,13 +398,7 @@ def nameserver(self): def to_intermediate(self): result = [] - result.append({ - 'host': [{ - 'name': self.hostname, - 'status': 'enabled', - }] - }) - + result.append(self.host()) result.append(self.nameserver()) From 7068560073ed575cf8a3696797bca9fe8655a171 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 7 Jul 2017 10:35:23 +0200 Subject: [PATCH 087/342] added docs about `gui` extension key in airos --- docs/source/backends/airos.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 96a754a00..dd799c7cf 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -95,6 +95,22 @@ DNS servers ----------- +GUI +--- + +As an extension to `NetJSON ` you can use the ``gui`` key to set the language of the interface and show the advanced network configuration option. + +The default values for this key are as reported below + +.. code-block:: json + + { + "gui": { + "language": "en_US", + "advanced": true + } + } + WPA2 ---- From 06835792636e5d36299acf345b605f882decbfab Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 7 Jul 2017 10:36:02 +0200 Subject: [PATCH 088/342] warn user that wpa2_enterprise is not supported by netjsonconfig --- docs/source/backends/airos.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index dd799c7cf..f7a52b208 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -135,7 +135,7 @@ As an example here is a snippet that set the authentication protocol to WPA2 per ] } -And another that set the authentication protocol to WPA2 enterprise +And another that set the authentication protocol to WPA2 enterprise, but this is still not supported by netjsonconfig .. code-block:: json From b2a0f7a87de0b8ab84340a31bdfdeff479aa324e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 21 Jun 2017 11:32:48 +0200 Subject: [PATCH 089/342] [airos][schema] added definition for airos interface roles --- netjsonconfig/backends/airos/converters.py | 3 +++ netjsonconfig/backends/airos/schema.py | 28 ++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 9830b1b21..cbeb1238a 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -278,6 +278,9 @@ def to_intermediate(self): for addr in addresses: temp = deepcopy(base) + if addr.get('management'): + temp['role'] = self.type_to_role(interface['type']) + # handle explicit address policy if addr['proto'] == 'dhcp': temp['autoip'] = {} diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index e479e6679..46ea06459 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -5,6 +5,31 @@ from ...schema import DEFAULT_FILE_MODE # noqa - backward compatibility from ...utils import merge_config +""" +This defines a new property in the ``Interface``. + +The management interface is the one that exposes the +web interface + +It can be used on a single interface (ethernet, vlan) or +on a bridge +""" + +netconf_schema = { + "type": "object", + "addtionalProperties": True, + "definitions": { + "base_address": { + "properties": { + "management": { + "type": "boolean", + "default": False, + } + } + } + } + } + """ This schema override the possible encryption for AirOS from the default schema """ @@ -42,5 +67,4 @@ } - -schema = merge_config(default_schema, wpasupplicant_schema) +schema = merge_config(default_schema, netconf_schema, wpasupplicant_schema) From cae9a4952910952ecf7e3ab152186983e208cd5d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 26 Jun 2017 11:10:13 +0200 Subject: [PATCH 090/342] [airos] finisched working on ntp converter --- netjsonconfig/backends/airos/converters.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index cbeb1238a..3a49fead2 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -320,10 +320,21 @@ def to_intermediate(self): class Ntpclient(BaseConverter): - netjson_key = 'general' + netjson_key = 'ntp_servers' def to_intermediate(self): result = [] + temp = [] + + original = get_copy(self.netjson, self.netjson_key) + + for ntp in original: + temp.append({ + 'server': ntp, + 'status': 'enabled', + }) + + result.append(temp) result.append({ 'status': 'disabled', From 1635c964a1b68726688e11c13f674065372ae7b9 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 26 Jun 2017 11:19:42 +0200 Subject: [PATCH 091/342] [airos][docs] documented the "ntp_servers" netjson key --- docs/source/backends/airos.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index f7a52b208..7d736e279 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -111,6 +111,23 @@ The default values for this key are as reported below } } +NTP servers +----------- + +This is an extension to the `NetJSON` specification. + +By setting the key ``ntp_servers`` in your input you can provide a list of ntp servers to use. + +.. code-block:: json + + { + "type": "DeviceConfiguration", + ... + "ntp_servers": [ + "0.ubnt.pool.ntp.org" + ] + } + WPA2 ---- From b13cd6d2e8b5e0f250669f4eee2b643aba3f92bc Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 26 Jun 2017 12:17:38 +0200 Subject: [PATCH 092/342] [airos] updated wireless converter to AirOS v8.3 --- netjsonconfig/backends/airos/converters.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 3a49fead2..6010175d8 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -624,6 +624,18 @@ def to_intermediate(self): 'addmtikie': 'enabled', 'devname': w['wireless']['radio'], 'hide_ssid': 'enabled' if w['wireless'].get('hidden') else 'disabled', + 'l2_isolation': 'disabled', + 'mac_acl': { + 'policy': 'allow', + 'status': 'disabled', + }, + 'mcast': { + 'enhance': 0, + }, + 'rate': { + 'auto': 'enabled', + 'mcs': -1, + }, 'security': { 'type': 'none', }, From c00677707e4a96c439eb352fbcd0d3f698d71b1c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 10 Jul 2017 12:40:01 +0200 Subject: [PATCH 093/342] [airos] update ntpclient converter --- netjsonconfig/backends/airos/converters.py | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 6010175d8..742828755 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -326,19 +326,24 @@ def to_intermediate(self): result = [] temp = [] - original = get_copy(self.netjson, self.netjson_key) + original = get_copy(self.netjson, self.netjson_key, []) + + if original: + for ntp in original: + temp.append({ + 'server': ntp, + 'status': 'enabled', + }) - for ntp in original: - temp.append({ - 'server': ntp, + result.append(temp) + result.append({ 'status': 'enabled', }) + else: + result.append({ + 'status': 'disabled', + }) - result.append(temp) - - result.append({ - 'status': 'disabled', - }) return (('ntpclient', result),) From e238e5cff5ec383e3e0e77ab9991d7286501e863 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 11 Jul 2017 12:30:55 +0200 Subject: [PATCH 094/342] [wpasupplicant] implemented different paths as wpasupplicant can produce both station and access ppoint mode and the intermediate representation is too different between the two this splits the two mode in different functions --- netjsonconfig/backends/airos/converters.py | 91 +++++++++++++++---- netjsonconfig/backends/airos/wpasupplicant.py | 50 ++++++++-- 2 files changed, 116 insertions(+), 25 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 742828755..d041b11c6 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,7 +4,7 @@ from ipaddress import ip_interface -from wpasupplicant import available_encryption_protocols +from wpasupplicant import available_mode_authentication def status(config, key='disabled'): if config.get(key): @@ -667,12 +667,10 @@ def to_intermediate(self): class Wpasupplicant(BaseConverter): netjson_key = 'interfaces' - def to_intermediate(self): + def _station_intermediate(self, original): result = [] + station_auth_protocols = available_mode_authentication['station'] - original = [ - i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'wireless' - ] temp_dev = { 'profile': 'AUTO', @@ -686,12 +684,17 @@ def to_intermediate(self): temp_dev['devname'] = head['wireless']['radio'] if 'encryption' in head: - network = available_encryption_protocols.get(head['encryption']['protocol'])(head) + network = station_auth_protocols.get(head['encryption']['protocol'])(head) else: - network = available_encryption_protocols['none'](head) - del temp_dev['driver'] - del temp_dev['devname'] + # early return as wpasupplicant is not + # configured for station mode without + # encryption + return (('wpasupplicant', []),) + +# network = available_auth_proto['none'](head) +# del temp_dev['driver'] +# del temp_dev['devname'] result.append({ 'device': [ @@ -702,15 +705,42 @@ def to_intermediate(self): 'name': 'AUTO', 'network': [ network, - { - 'key_mgmt': [ - { - 'name': 'NONE', - }, - ], - 'priority': 2, - 'status': 'disabled', - }, + self.secondary_network(), + ], + }, + ], + }) + result.append({ + 'status': 'enabled', + }) + + return (('wpasupplicant', result),) + + def _access_point_intermediate(self, original): + """ + Intermediate representation for ``access_point`` mode + + wpasupplicant.device is missing when using the ``access_point`` mode + to the temp_dev will not be generated + """ + result = [] + ap_auth_protocols = available_mode_authentication['access_point'] + + if original: + head = original[0] + + if 'encryption' in head: + network = ap_auth_protocols.get(head['encryption']['protocol'])(head) + + else: + network = ap_auth_protocols['none'](head) + + result.append({ + 'profile': [ + { + 'network': [ + network, + self.secondary_network(), ], }, ], @@ -720,3 +750,28 @@ def to_intermediate(self): }) return (('wpasupplicant', result),) + + def secondary_network(self): + """ + The default secondary network configuration + """ + return { + 'key_mgmt': [ + { + 'name': 'NONE', + }, + ], + 'priority': 2, + 'status': 'disabled', + } + + def to_intermediate(self): + original = [ + i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'wireless' + ] + + if original: + head = original[0] + # call either ``_station_intermediate`` or ``_access_point_intermediate`` + # and return the result + return getattr(self, '_%s_intermediate' % head['wireless']['mode'])(original) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index 4ce95f4d2..ff534ae60 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -1,8 +1,36 @@ -def no_encryption(interface): +def ap_no_encryption(interface): """ Returns the wpasupplicant.profile.1.network for encryption None as the intermediate dict """ + return { + 'ssid': interface['wireless']['ssid'], + 'key_mgmt': [ + { + 'name': 'NONE', + }, + ], + } + + +def ap_wpa2_personal(interface): + """ + Returns the wpasupplicant.profile.1.network + for wpa2_personal as the indernediate dict + in ``access_point`` mode + """ + return { + 'psk': interface['encryption']['key'], + 'ssid': interface['wireless']['ssid'], + } + + +def sta_no_encryption(interface): + """ + Returns the wpasupplicant.profile.1.network + for encryption None as the intermediate dict + in ``station`` mode + """ return { 'ssid': interface['wireless']['ssid'], 'priority': 100, @@ -14,10 +42,11 @@ def no_encryption(interface): } -def wpa2_personal(interface): +def sta_wpa2_personal(interface): """ Returns the wpasupplicant.profile.1.network for wpa2_personal as the indernediate dict + in ``station`` mode """ return { 'phase2=auth': 'MSCHAPV2', @@ -47,7 +76,7 @@ def wpa2_personal(interface): } -def wpa2_enterprise(interface): +def sta_wpa2_enterprise(interface): """ Returns the wpasupplicant.profile.1.network for wpa2_enterprise as the intermediate dict @@ -82,8 +111,15 @@ def wpa2_enterprise(interface): ], } -available_encryption_protocols = { - 'none': no_encryption, - 'wpa2_personal': wpa2_personal, - 'wpa2_enterprise': wpa2_enterprise, + +available_mode_authentication = { + 'access_point': { + 'none': ap_no_encryption, + 'wpa2_personal': ap_wpa2_personal, + }, + 'station': { + 'none': sta_no_encryption, + 'wpa2_personal': sta_wpa2_personal, +# 'wpa2_enterprise': sta_wpa2_enterprise, + }, } From d0b87fc4a56648923a430c9870c486f8d7c5515d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 11 Jul 2017 13:19:59 +0200 Subject: [PATCH 095/342] [wpasupplicant] order the default arguments on bottom --- netjsonconfig/backends/airos/converters.py | 12 +++------ netjsonconfig/backends/airos/wpasupplicant.py | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index d041b11c6..80af94476 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -671,7 +671,6 @@ def _station_intermediate(self, original): result = [] station_auth_protocols = available_mode_authentication['station'] - temp_dev = { 'profile': 'AUTO', 'status': 'enabled', @@ -687,14 +686,9 @@ def _station_intermediate(self, original): network = station_auth_protocols.get(head['encryption']['protocol'])(head) else: - # early return as wpasupplicant is not - # configured for station mode without - # encryption - return (('wpasupplicant', []),) - -# network = available_auth_proto['none'](head) -# del temp_dev['driver'] -# del temp_dev['devname'] + network = station_auth_protocols['none'](head) + del temp_dev['driver'] + del temp_dev['devname'] result.append({ 'device': [ diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index ff534ae60..a8f83884c 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -49,28 +49,33 @@ def sta_wpa2_personal(interface): in ``station`` mode """ return { - 'phase2=auth': 'MSCHAPV2', + 'ssid': interface['wireless']['ssid'], + 'psk': interface['encryption']['key'], + # no advanced authentication methods + # with psk 'eap': [ { 'status': 'disabled', }, ], - 'psk': interface['encryption']['key'], - 'pairwise': [ + 'key_mgmt': [ { - 'name': 'CCMP', + 'name': 'WPA-PSK', }, ], - 'proto': [ + 'pairwise': [ { - 'name': 'RSN', + 'name': 'CCMP', }, ], - 'ssid': interface['wireless']['ssid'], + # this may be not necessary + # as further authentication is not + # supported + 'phase2=auth': 'MSCHAPV2', 'priority': 100, - 'key_mgmt': [ + 'proto': [ { - 'name': 'WPA-PSK', + 'name': 'RSN', }, ], } @@ -82,6 +87,7 @@ def sta_wpa2_enterprise(interface): for wpa2_enterprise as the intermediate dict """ return { + 'ssid': interface['wireless']['ssid'], 'phase2=auth': 'MSCHAPV2', 'eap': [ { @@ -102,7 +108,6 @@ def sta_wpa2_enterprise(interface): 'name': 'RSN', }, ], - 'ssid': interface['wireless']['ssid'], 'priority': 100, 'key_mgmt': [ { From a830852d99ed62a7aceb73a3c50634480dd03485 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 5 Jun 2017 12:51:29 +0200 Subject: [PATCH 096/342] [airos][test] begin airos testing --- tests/airos/__init__.py | 0 tests/airos/dummy.py | 15 ++- tests/airos/test_bridge.py | 203 +++++++++++++++++++++++++++++++++++ tests/airos/test_system.py | 60 ++++++++--- tests/airos/test_vlan.py | 3 - tests/airos/test_wireless.py | 28 ++--- 6 files changed, 273 insertions(+), 36 deletions(-) create mode 100644 tests/airos/__init__.py create mode 100644 tests/airos/test_bridge.py diff --git a/tests/airos/__init__.py b/tests/airos/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/airos/dummy.py b/tests/airos/dummy.py index 355e3467d..d9096006d 100644 --- a/tests/airos/dummy.py +++ b/tests/airos/dummy.py @@ -3,13 +3,22 @@ from netjsonconfig.backends.airos.converters import * -class VlanAirOS(AirOS): +class BridgeAirOS(AirOS): converters = [ - Vlan, + Bridge, ] - class ResolvAirOS(AirOS): converters = [ Resolv, ] + +class VlanAirOS(AirOS): + converters = [ + Vlan, + ] + +class WirelessAirOS(AirOS): + converters = [ + Wireless, + ] diff --git a/tests/airos/test_bridge.py b/tests/airos/test_bridge.py new file mode 100644 index 000000000..fecb4e996 --- /dev/null +++ b/tests/airos/test_bridge.py @@ -0,0 +1,203 @@ +import unittest + +from netjsonconfig.backends.airos.converters import * + +from .dummy import BridgeAirOS + + +class TestBridgeConverter(unittest.TestCase): + """ + tests for backends.airos.renderers.SystemRenderer + """ + backend = BridgeAirOS + + def test_active_bridge(self): + + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0", + "disabled": False, + }, + { + "type": "ethernet", + "name": "eth1", + "disabled": False, + }, + { + "type": "bridge", + "name": "br0", + "bridge_members": [ + "eth0", + "eth1", + ], + "disabled": False, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + '1.comment': '', + '1.devname': 'br0', + '1.port.1.devname': 'eth0', + '1.port.1.status': 'enabled', + '1.port.2.devname': 'eth1', + '1.port.2.status': 'enabled', + '1.status': 'enabled', + '1.stp.status': 'enabled' + }, + { + 'status': 'enabled', + } + ] + + self.assertEqual(o.intermediate_data['bridge'], expected) + + def test_disabled_bridge(self): + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0", + "disabled": False, + }, + { + "type": "ethernet", + "name": "eth1", + "disabled": False, + }, + { + "type": "bridge", + "name": "br0", + "bridge_members": [ + "eth0", + "eth1", + ], + "disabled": True, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + '1.comment': '', + '1.devname': 'br0', + '1.port.1.devname': 'eth0', + '1.port.1.status': 'enabled', + '1.port.2.devname': 'eth1', + '1.port.2.status': 'enabled', + '1.status': 'disabled', + '1.stp.status': 'enabled' + }, + { + 'status': 'enabled', + } + ] + + self.assertEqual(o.intermediate_data['bridge'], expected) + + def test_many_bridges(self): + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0", + "disabled": False, + }, + { + "type": "ethernet", + "name": "eth1", + "disabled": False, + }, + { + "type": "bridge", + "name": "br0", + "bridge_members": [ + "eth0", + "eth1", + ], + "disabled": True, + }, + { + "type": "ethernet", + "name": "eth2", + "disabled": False, + }, + { + "type": "ethernet", + "name": "eth3", + "disabled": False, + }, + { + "type": "br1dge", + "name": "br1", + "bridge_members": [ + "eth1", + "eth2", + ], + "disabled": False, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + '1.comment': '', + '1.devname': 'br0', + '1.port.1.devname': 'eth0', + '1.port.1.status': 'enabled', + '1.port.2.devname': 'eth1', + '1.port.2.status': 'enabled', + '1.status': 'disabled', + '1.stp.status': 'enabled' + }, + { + '2.comment': '', + '2.devname': 'br1', + '2.port.1.devname': 'eth2', + '2.port.1.status': 'enabled', + '2.port.2.devname': 'eth3', + '2.port.2.status': 'enabled', + '2.status': 'enabled', + '2.stp.status': 'enabled' + }, + { + 'status': 'enabled', + } + ] + + self.assertEqual(o.intermediate_data['bridge'], expected) + + def test_no_vlan(self): + o = self.backend({ + "interfaces": [ + { + "type": "ethernet", + "name": "eth0", + "disabled": False, + }, + { + "type": "ethernet", + "name": "eth1", + "disabled": False, + }, + ] + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'enabled', + } + ] + + self.assertEqual(o.intermediate_data['bridge'], expected) diff --git a/tests/airos/test_system.py b/tests/airos/test_system.py index d9259e7da..13299fbb8 100644 --- a/tests/airos/test_system.py +++ b/tests/airos/test_system.py @@ -1,23 +1,51 @@ import unittest -from netjsonconfig import AirOS -from netjsonconfig.exceptions import ValidationError -from netjsonconfig.utils import _TabsMixin +from .dummy import ResolvAirOS -class TestSystemRenderer(unittest.TestCase, _TabsMixin): - """ - tests for backends.airos.renderers.SystemRenderer - """ +class TestResolvConverter(unittest.TestCase): + + backend = ResolvAirOS + def test_resolv(self): - o = AirOS({ - "dns_server": [ + o = self.backend({ + "dns_servers": [ "10.150.42.1" - ] + ], }) - expected = self._tabs("""resolv.status=disabled -resolv.nameserver.status=enabled -resolv.nameserver.1.status=enabled -resolv.nameserver.1.ip=10.150.42.1 -""") - self.assertEqual(o.render(), expected) + + o.to_intermediate() + + expected = [ + { + 'host.1.name' : '', + }, + { + 'nameserver.1.ip' : '10.150.42.1', + 'nameserver.1.status' : 'enabled', + }, + { + 'status' : 'enabled', + }, + ] + + + self.assertEqual(o.intermediate_data['resolv'], expected) + + def test_no_dns_server(self): + o = self.backend({ + "dns_servers": [], + }) + + o.to_intermediate() + + expected = [ + { + 'host.1.name' : '', + }, + { + 'status' : 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['resolv'], expected) diff --git a/tests/airos/test_vlan.py b/tests/airos/test_vlan.py index 79d407a36..6674c7524 100644 --- a/tests/airos/test_vlan.py +++ b/tests/airos/test_vlan.py @@ -1,9 +1,6 @@ import unittest -from collections import OrderedDict - from netjsonconfig.backends.airos.converters import * -from netjsonconfig.exceptions import ValidationError from .dummy import VlanAirOS diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index 5a1509a96..a2392e6ba 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -16,21 +16,21 @@ def test_active_wireless(self): { "type": "wireless", "name": "wlan0", - "mac" : "de:9f:db:30:c9:c5", - "mtu" : 1500, - "txqueuelen" : 1000, - "autostart" : True, - "wireless" : { - "radio" : "radio0", - "mode" : "access_point", - "ssid" : "ap-ssid-example", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "autostart": True, + "wireless": { + "radio": "radio0", + "mode": "access_point", + "ssid": "ap-ssid-example", }, - "addresses" : [ + "addresses": [ { - "address" : "192.168.1.1", - "mask" : 24, - "family" : "ipv4", - "proto" : "static", + "address": "192.168.1.1", + "mask": 24, + "family": "ipv4", + "proto": "static", } ], } @@ -50,7 +50,7 @@ def test_active_wireless(self): '1.wds.status': 'enabled', }, { - 'status' : 'enabled', + 'status': 'enabled', } ] From 33d748149049f4506f16d34a330b2f578ec7d2e4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 9 Jun 2017 09:45:35 +0200 Subject: [PATCH 097/342] fix broken tests by adding default values --- tests/airos/test_bridge.py | 4 ++-- tests/airos/test_wireless.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/airos/test_bridge.py b/tests/airos/test_bridge.py index fecb4e996..5c3a56090 100644 --- a/tests/airos/test_bridge.py +++ b/tests/airos/test_bridge.py @@ -135,11 +135,11 @@ def test_many_bridges(self): "disabled": False, }, { - "type": "br1dge", + "type": "bridge", "name": "br1", "bridge_members": [ - "eth1", "eth2", + "eth3", ], "disabled": False, } diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index a2392e6ba..92a4557d8 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -45,6 +45,11 @@ def test_active_wireless(self): '1.devname': 'wlan0', '1.hide_ssid': 'disabled', '1.security.type': 'none', + '1.signal_led1': 75, + '1.signal_led2': 50, + '1.signal_led3': 25, + '1.signal_led4': 15, + '1.signal_led_status': 'enabled', '1.ssid': 'ap-ssid-example', '1.status': 'enabled', '1.wds.status': 'enabled', From ada21ac4e068296ecc234efe28293d34ece00493 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 9 Jun 2017 10:09:43 +0200 Subject: [PATCH 098/342] [airos][test] add all the possible mocked backend --- tests/airos/dummy.py | 187 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/tests/airos/dummy.py b/tests/airos/dummy.py index d9096006d..3a68f195d 100644 --- a/tests/airos/dummy.py +++ b/tests/airos/dummy.py @@ -3,22 +3,209 @@ from netjsonconfig.backends.airos.converters import * +class AaaAirOS(AirOS): + """ + Mock backend with converter for radius authentication + """ + converters = [ + Aaa, + ] + + class BridgeAirOS(AirOS): + """ + Mock backend with converter for bridge interface + """ converters = [ Bridge, ] + +class DiscoveryAirOS(AirOS): + """ + Mock backend with converter for network hardware discovery + """ + converters = [ + Discovery, + ] + + +class DyndnsAirOS(AirOS): + """ + Mock backend with converter for dynamic dns capabilities + """ + converters = [ + Dyndns, + ] + + +class GuiAirOS(AirOS): + """ + Mock backend with converter for web interface settings + """ + converters = [ + Gui, + ] + + +class HttpdAirOS(AirOS): + """ + Mock backend with converter for web server + """ + converters = [ + Httpd, + ] + + +class NetconfAirOS(AirOS): + """ + Mock backend with converter for network configuration + """ + converters = [ + Netconf, + ] + + +class Netmode(AirOS): + """ + Mock backend with converter for network mode + """ + converters = [ + Netmode, + ] + + +class Ntpclient(AirOS): + """ + Mock backend with converter for ntp settings + """ + converters = [ + Ntpclient, + ] + + +class PwdogAirOS(AirOS): + """ + Mock backend with converter for ping watchdog settings + """ + converters = [ + Pwdog, + ] + + +class RadioAirOS(AirOS): + """ + Mock backend with converter for radio settings + """ + converters = [ + Radio, + ] + + class ResolvAirOS(AirOS): + """ + Mock backend with converter for network resolver + """ converters = [ Resolv, ] + +class RouteAirOS(AirOS): + """ + Mock backend with converter for static routes + """ + converters = [ + Route, + ] + + +class SnmpAirOS(AirOS): + """ + Mock backend with converter for simple network management protocol + """ + converters = [ + Snmp, + ] + + + +class SshdAirOS(AirOS): + """ + Mock backend with converter for ssh daemon settings + """ + converters = [ + Sshd, + ] + + +class SyslogAirOS(AirOS): + """ + Mock backend with converter for remote logging + """ + converters = [ + Syslog, + ] + + +class SystemAirOS(AirOS): + """ + Mock backend with converter for system settings + """ + converters = [ + System, + ] + + +class TelnetdAirOS(AirOS): + """ + Mock backend with converter for telnet daemon settings + """ + converters = [ + Telnetd, + ] + + +class UpdateAirOS(AirOS): + """ + Mock backend with converter for update + """ + converters = [ + Update, + ] + + +class UsersAirOS(AirOS): + """ + Mock backend with converter for users settings + """ + converters = [ + Users, + ] + + class VlanAirOS(AirOS): + """ + Mock backend with converter for vlan settings + """ converters = [ Vlan, ] + class WirelessAirOS(AirOS): + """ + Mock backend with converter for wireless settings + """ converters = [ Wireless, ] + + +class WpasupplicantAirOS(AirOS): + """ + Mock backend with converter for wpasupplicant settings + """ + converters = [ + Wpasupplicant, + ] From fabc6fbd8e5880e9a20edbc5940aeec90f87d0c0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 9 Jun 2017 12:55:20 +0200 Subject: [PATCH 099/342] [airos][test] add tests for airos converters --- tests/airos/test_aaa.py | 24 ++ tests/airos/test_discovery.py | 29 +++ tests/airos/test_dyndns.py | 24 ++ tests/airos/test_gui.py | 31 +++ tests/airos/test_httpd.py | 34 +++ tests/airos/test_netconf.py | 457 ++++++++++++++++++++++++++++++++++ tests/airos/test_ntp.py | 24 ++ tests/airos/test_pwdog.py | 26 ++ tests/airos/test_radio.py | 79 ++++++ 9 files changed, 728 insertions(+) create mode 100644 tests/airos/test_aaa.py create mode 100644 tests/airos/test_discovery.py create mode 100644 tests/airos/test_dyndns.py create mode 100644 tests/airos/test_gui.py create mode 100644 tests/airos/test_httpd.py create mode 100644 tests/airos/test_netconf.py create mode 100644 tests/airos/test_ntp.py create mode 100644 tests/airos/test_pwdog.py create mode 100644 tests/airos/test_radio.py diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py new file mode 100644 index 000000000..f9e646be3 --- /dev/null +++ b/tests/airos/test_aaa.py @@ -0,0 +1,24 @@ +import unittest + +from .dummy import AaaAirOS + + +class TestResolvConverter(unittest.TestCase): + + backend = AaaAirOS + + def test_aaa_key(self): + o = self.backend({ + "general": {} + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'disabled', + }, + ] + + + self.assertEqual(o.intermediate_data['aaa'], expected) diff --git a/tests/airos/test_discovery.py b/tests/airos/test_discovery.py new file mode 100644 index 000000000..c613b88ab --- /dev/null +++ b/tests/airos/test_discovery.py @@ -0,0 +1,29 @@ +import unittest + +from .dummy import DiscoveryAirOS + + +class TestDiscoveryConverter(unittest.TestCase): + + backend = DiscoveryAirOS + + def test_discovery_key(self): + o = self.backend({ + "general": {} + }) + + o.to_intermediate() + + expected = [ + { + 'cdp': { + 'status': 'enabled', + } + }, + { + 'status': 'enabled', + }, + ] + + + self.assertEqual(o.intermediate_data['discovery'], expected) diff --git a/tests/airos/test_dyndns.py b/tests/airos/test_dyndns.py new file mode 100644 index 000000000..4195c2554 --- /dev/null +++ b/tests/airos/test_dyndns.py @@ -0,0 +1,24 @@ +import unittest + +from .dummy import DyndnsAirOS + + +class TestDyndnsConverter(unittest.TestCase): + + backend = DyndnsAirOS + + def test_Dyndns_key(self): + o = self.backend({ + "general": {} + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'disabled', + }, + ] + + + self.assertEqual(o.intermediate_data['dyndns'], expected) diff --git a/tests/airos/test_gui.py b/tests/airos/test_gui.py new file mode 100644 index 000000000..41312dece --- /dev/null +++ b/tests/airos/test_gui.py @@ -0,0 +1,31 @@ +import unittest + +from .dummy import GuiAirOS + + +class TestGuiConverter(unittest.TestCase): + + backend = GuiAirOS + + def test_gui_key(self): + o = self.backend({ + "general": {} + }) + + o.to_intermediate() + + expected = [ + { + 'network': { + 'advanced': { + 'status': 'enabled', + }, + } + }, + { + 'language': 'en_US', + }, + ] + + + self.assertEqual(o.intermediate_data['gui'], expected) diff --git a/tests/airos/test_httpd.py b/tests/airos/test_httpd.py new file mode 100644 index 000000000..f0bbd1278 --- /dev/null +++ b/tests/airos/test_httpd.py @@ -0,0 +1,34 @@ +import unittest + +from .dummy import HttpdAirOS + + +class TestHttpdConverter(unittest.TestCase): + + backend = HttpdAirOS + + def test_httpd_key(self): + o = self.backend({ + "general": {} + }) + + o.to_intermediate() + + expected = [ + { + 'https': { + 'port': 443, + 'status': 'enabled', + }, + }, + { + 'port': 80, + 'session': { + 'timeout': 9000, + }, + 'status': 'enabled', + }, + ] + + + self.assertEqual(o.intermediate_data['httpd'], expected) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py new file mode 100644 index 000000000..67452e2e3 --- /dev/null +++ b/tests/airos/test_netconf.py @@ -0,0 +1,457 @@ +import unittest + +from .dummy import NetconfAirOS + + +class TestNetconfConverter(unittest.TestCase): + + backend = NetconfAirOS + + def test_netconf_key(self): + o = self.backend({ + "intefaces": [] + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'enabled', + }, + ] + + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_active_interface(self): + o = self.backend({ + "intefaces": [{ + 'name': 'eth0', + 'type': 'ethernet', + }] + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'eth0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_inactive_interface(self): + o = self.backend({ + "intefaces": [{ + 'name': 'eth0', + 'type': 'ethernet', + 'disabled': True, + }] + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'eth0', + '1.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] + + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_vlan(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'eth0.1', + 'type': 'ethernet', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'eth0.1', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_management_vlan(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'eth0.1', + 'type': 'ethernet', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'eth0.1', + '1.status': 'enabled', + '1.role': 'mlan', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_access_point(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'access_point', + 'ssid': 'ap-ssid-example', + } + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'wlan0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_station(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'station', + 'ssid': 'ap-ssid-example', + } + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'wlan0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_adhoc(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'adhoc', + 'ssid': 'ap-ssid-example', + } + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'wlan0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_wds(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'wds', + 'ssid': 'ap-ssid-example', + } + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'wlan0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_monitor(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'monitor', + 'ssid': 'ap-ssid-example', + } + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'wlan0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_80211s(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': '802.11s', + 'ssid': 'ap-ssid-example', + } + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'wlan0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_bridge(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'br0', + 'type': 'bridge', + 'bridge_members': [ + 'eth0', + 'eth1', + ], + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'br0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_virtual(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'veth0', + 'type': 'virtual', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'veth0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_loopback(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'lopp0', + 'type': 'loopback', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'loop0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_other(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'fancyname0', + 'type': 'other', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'fancyname0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_more_interfaces(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'eth0', + 'type': 'ethernet', + }, + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'station', + 'ssid': 'ap-ssid-example', + } + }, + { + 'name': 'br0', + 'type': 'bridge', + 'bridge_members': [ + 'eth0', + 'wlan0', + ], + }, + { + 'name': 'veth0', + 'type': 'virtual', + }, + { + 'name': 'loop0', + 'type': 'loopback', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'eth0', + '1.status': 'enabled', + }, + { + '2.devname': 'wlan0', + '2.status': 'enabled', + }, + { + '3.devname': 'br0', + '3.status': 'enabled', + }, + { + '4.devname': 'veth0', + '4.status': 'enabled', + }, + { + '5.devname': 'loop0', + '5.status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + diff --git a/tests/airos/test_ntp.py b/tests/airos/test_ntp.py new file mode 100644 index 000000000..408953904 --- /dev/null +++ b/tests/airos/test_ntp.py @@ -0,0 +1,24 @@ +import unittest + +from .dummy import NtpclientAirOS + + +class TestResolvConverter(unittest.TestCase): + + backend = NtpclientAirOS + + def test_ntp_key(self): + o = self.backend({ + "general": {} + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'disabled', + }, + ] + + + self.assertEqual(o.intermediate_data['ntpclient'], expected) diff --git a/tests/airos/test_pwdog.py b/tests/airos/test_pwdog.py new file mode 100644 index 000000000..a57854d0a --- /dev/null +++ b/tests/airos/test_pwdog.py @@ -0,0 +1,26 @@ +import unittest + +from .dummy import PwdogAirOS + + +class TestPwdogConverter(unittest.TestCase): + + backend = PwdogAirOS + + def test_ntp_key(self): + o = self.backend({ + "general": {} + }) + + o.to_intermediate() + + expected = [ + { + 'delay': 300, + 'period': 300, + 'retry': 3, + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['pwdog'], expected) diff --git a/tests/airos/test_radio.py b/tests/airos/test_radio.py new file mode 100644 index 000000000..0f6b70b79 --- /dev/null +++ b/tests/airos/test_radio.py @@ -0,0 +1,79 @@ +import unittest + +from .dummy import RadioAirOS + + +class TestRadioConverter(unittest.TestCase): + + backend = RadioAirOS + + def test_no_radio(self): + o = self.backend({ + "radios": [] + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['radio'], expected) + + def test_active_radio(self): + o = self.backend({ + "radios": [ + { + 'name': 'ath0', + 'channel': 1, + 'channel_width': 20, + 'disabled': False, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'ath0', + '1.status': 'enabled', + '1.txpower': '', + '1.chanbw': 20, + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['radio'], expected) + + def test_inactive_radio(self): + o = self.backend({ + "radios": [ + { + 'name': 'ath0', + 'channel': 1, + 'channel_width': 20, + 'disabled': True, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'ath0', + '1.status': 'disabled', + '1.txpower': '', + '1.chanbw': 20, + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['radio'], expected) From 089321658174a497e25ccfd4017a7544350f04c6 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 9 Jun 2017 12:55:47 +0200 Subject: [PATCH 100/342] [airos][test] add test for no bridge interface --- tests/airos/test_bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/airos/test_bridge.py b/tests/airos/test_bridge.py index 5c3a56090..8423b2730 100644 --- a/tests/airos/test_bridge.py +++ b/tests/airos/test_bridge.py @@ -176,7 +176,7 @@ def test_many_bridges(self): self.assertEqual(o.intermediate_data['bridge'], expected) - def test_no_vlan(self): + def test_no_bridge(self): o = self.backend({ "interfaces": [ { From 6d88b6546eb625da29b6715f9515857fd0a8ee7b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 12 Jun 2017 13:53:19 +0200 Subject: [PATCH 101/342] [test] fixed broken expected value in test --- tests/airos/test_discovery.py | 6 +----- tests/airos/test_httpd.py | 10 +++------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/airos/test_discovery.py b/tests/airos/test_discovery.py index c613b88ab..efa82dc53 100644 --- a/tests/airos/test_discovery.py +++ b/tests/airos/test_discovery.py @@ -16,11 +16,7 @@ def test_discovery_key(self): expected = [ { - 'cdp': { - 'status': 'enabled', - } - }, - { + 'cdp.status': 'enabled', 'status': 'enabled', }, ] diff --git a/tests/airos/test_httpd.py b/tests/airos/test_httpd.py index f0bbd1278..b094f217f 100644 --- a/tests/airos/test_httpd.py +++ b/tests/airos/test_httpd.py @@ -16,16 +16,12 @@ def test_httpd_key(self): expected = [ { - 'https': { - 'port': 443, - 'status': 'enabled', - }, + 'https.port': 443, + 'https.status': 'enabled', }, { 'port': 80, - 'session': { - 'timeout': 9000, - }, + 'session.timeout': 900, 'status': 'enabled', }, ] From 353a026ff0f0f324e42b489c16c007e51c4de8b4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 12 Jun 2017 13:53:55 +0200 Subject: [PATCH 102/342] [test] added test for inactive radio --- tests/airos/test_wireless.py | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index 92a4557d8..cf73eb770 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -60,3 +60,56 @@ def test_active_wireless(self): ] self.assertEqual(o.intermediate_data['wireless'], expected) + + def test_inactive_wireless(self): + + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "autostart": True, + "disabled": True, + "wireless": { + "radio": "radio0", + "mode": "access_point", + "ssid": "ap-ssid-example", + }, + "addresses": [ + { + "address": "192.168.1.1", + "mask": 24, + "family": "ipv4", + "proto": "static", + } + ], + } + ] + }) + + o.to_intermediate() + + expected = [ + { + '1.addmtikie': 'enabled', + '1.devname': 'wlan0', + '1.hide_ssid': 'disabled', + '1.security.type': 'none', + '1.signal_led1': 75, + '1.signal_led2': 50, + '1.signal_led3': 25, + '1.signal_led4': 15, + '1.signal_led_status': 'enabled', + '1.ssid': 'ap-ssid-example', + '1.status': 'disabled', + '1.wds.status': 'enabled', + }, + { + 'status': 'enabled', + } + ] + + self.assertEqual(o.intermediate_data['wireless'], expected) From ca1f71a94a95c7575faca03dd063f4763ed05715 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 12 Jun 2017 14:52:53 +0200 Subject: [PATCH 103/342] [test] fixed not flattened expected values --- tests/airos/dummy.py | 18 +++++++++--------- tests/airos/test_gui.py | 8 ++------ tests/airos/test_netconf.py | 1 - tests/airos/test_radio.py | 6 ++++-- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/tests/airos/dummy.py b/tests/airos/dummy.py index 3a68f195d..e08eb9239 100644 --- a/tests/airos/dummy.py +++ b/tests/airos/dummy.py @@ -37,7 +37,7 @@ class DyndnsAirOS(AirOS): converters = [ Dyndns, ] - + class GuiAirOS(AirOS): """ @@ -46,7 +46,7 @@ class GuiAirOS(AirOS): converters = [ Gui, ] - + class HttpdAirOS(AirOS): """ @@ -55,7 +55,7 @@ class HttpdAirOS(AirOS): converters = [ Httpd, ] - + class NetconfAirOS(AirOS): """ @@ -64,7 +64,7 @@ class NetconfAirOS(AirOS): converters = [ Netconf, ] - + class Netmode(AirOS): """ @@ -73,16 +73,16 @@ class Netmode(AirOS): converters = [ Netmode, ] - -class Ntpclient(AirOS): + +class NtpclientAirOS(AirOS): """ Mock backend with converter for ntp settings """ converters = [ Ntpclient, ] - + class PwdogAirOS(AirOS): """ @@ -91,7 +91,7 @@ class PwdogAirOS(AirOS): converters = [ Pwdog, ] - + class RadioAirOS(AirOS): """ @@ -100,7 +100,7 @@ class RadioAirOS(AirOS): converters = [ Radio, ] - + class ResolvAirOS(AirOS): """ diff --git a/tests/airos/test_gui.py b/tests/airos/test_gui.py index 41312dece..1e224ce80 100644 --- a/tests/airos/test_gui.py +++ b/tests/airos/test_gui.py @@ -16,14 +16,10 @@ def test_gui_key(self): expected = [ { - 'network': { - 'advanced': { - 'status': 'enabled', - }, - } + 'language': 'en_US', }, { - 'language': 'en_US', + 'network.advanced.status': 'enabled', }, ] diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index 67452e2e3..6c5045dd5 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -20,7 +20,6 @@ def test_netconf_key(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) def test_active_interface(self): diff --git a/tests/airos/test_radio.py b/tests/airos/test_radio.py index 0f6b70b79..c80f00c31 100644 --- a/tests/airos/test_radio.py +++ b/tests/airos/test_radio.py @@ -30,6 +30,7 @@ def test_active_radio(self): 'channel': 1, 'channel_width': 20, 'disabled': False, + 'protocol': '802.11n', } ] }) @@ -38,10 +39,10 @@ def test_active_radio(self): expected = [ { + '1.chanbw': 20, '1.devname': 'ath0', '1.status': 'enabled', '1.txpower': '', - '1.chanbw': 20, }, { 'status': 'enabled', @@ -58,6 +59,7 @@ def test_inactive_radio(self): 'channel': 1, 'channel_width': 20, 'disabled': True, + 'protocol': '802.11n', } ] }) @@ -66,10 +68,10 @@ def test_inactive_radio(self): expected = [ { + '1.chanbw': 20, '1.devname': 'ath0', '1.status': 'disabled', '1.txpower': '', - '1.chanbw': 20, }, { 'status': 'enabled', From b7f7cd49590d031869105ecd2513750c896938d4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 22 Jun 2017 12:25:53 +0200 Subject: [PATCH 104/342] [airos][test] update tests to airos v8.3 configuration --- tests/airos/test_netconf.py | 75 +++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index 6c5045dd5..d84ac2502 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -36,13 +36,16 @@ def test_active_interface(self): { '1.devname': 'eth0', '1.status': 'enabled', + '1.up': 'enabled', + '1.flowcontrol.tx.status': 'enabled', + '1.flowcontrol.rx.status': 'enabled', + '1.autoip.status': 'disabled', }, { 'status': 'enabled', }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) def test_inactive_interface(self): @@ -59,7 +62,11 @@ def test_inactive_interface(self): expected = [ { '1.devname': 'eth0', - '1.status': 'disabled', + '1.status': 'enabled', + '1.up': 'disabled', + '1.flowcontrol.tx.status': 'enabled', + '1.flowcontrol.rx.status': 'enabled', + '1.autoip.status': 'disabled', }, { 'status': 'enabled', @@ -85,12 +92,14 @@ def test_vlan(self): { '1.devname': 'eth0.1', '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', }, { 'status': 'enabled', }, ] - + self.assertEqual(o.intermediate_data['netconf'], expected) def test_management_vlan(self): @@ -109,13 +118,15 @@ def test_management_vlan(self): { '1.devname': 'eth0.1', '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', '1.role': 'mlan', }, { 'status': 'enabled', }, ] - + self.assertEqual(o.intermediate_data['netconf'], expected) def test_access_point(self): @@ -137,14 +148,17 @@ def test_access_point(self): expected = [ { - '1.devname': 'wlan0', + '1.devname': 'ath0', + '1.mtu': 1500, '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', }, { 'status': 'enabled', }, ] - + self.assertEqual(o.intermediate_data['netconf'], expected) def test_station(self): @@ -166,14 +180,17 @@ def test_station(self): expected = [ { - '1.devname': 'wlan0', + '1.devname': 'ath0', '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + '1.autoip.status': 'disabled', }, { 'status': 'enabled', }, ] - + self.assertEqual(o.intermediate_data['netconf'], expected) def test_adhoc(self): @@ -195,14 +212,17 @@ def test_adhoc(self): expected = [ { - '1.devname': 'wlan0', + '1.devname': 'ath0', '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + '1.autoip.status': 'disabled', }, { 'status': 'enabled', }, ] - + self.assertEqual(o.intermediate_data['netconf'], expected) def test_wds(self): @@ -224,14 +244,17 @@ def test_wds(self): expected = [ { - '1.devname': 'wlan0', + '1.devname': 'ath0', + '1.mtu': 1500, + '1.up': 'enabled', '1.status': 'enabled', + '1.autoip.status': 'disabled', }, { 'status': 'enabled', }, ] - + self.assertEqual(o.intermediate_data['netconf'], expected) def test_monitor(self): @@ -253,8 +276,10 @@ def test_monitor(self): expected = [ { - '1.devname': 'wlan0', + '1.devname': 'ath0', '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'enabled', }, { 'status': 'enabled', @@ -282,8 +307,10 @@ def test_80211s(self): expected = [ { - '1.devname': 'wlan0', + '1.devname': 'ath0', '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', }, { 'status': 'enabled', @@ -312,12 +339,15 @@ def test_bridge(self): { '1.devname': 'br0', '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + '1.autoip.status': 'disabled', }, { 'status': 'enabled', }, ] - + self.assertEqual(o.intermediate_data['netconf'], expected) def test_virtual(self): @@ -341,7 +371,7 @@ def test_virtual(self): 'status': 'enabled', }, ] - + self.assertEqual(o.intermediate_data['netconf'], expected) def test_loopback(self): @@ -365,7 +395,7 @@ def test_loopback(self): 'status': 'enabled', }, ] - + self.assertEqual(o.intermediate_data['netconf'], expected) def test_other(self): @@ -433,14 +463,20 @@ def test_more_interfaces(self): { '1.devname': 'eth0', '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, }, { - '2.devname': 'wlan0', + '2.devname': 'ath0', '2.status': 'enabled', + '2.up': 'enabled', + '3.mtu': 1500, }, { '3.devname': 'br0', '3.status': 'enabled', + '3.up': 'enabled', + '3.mtu': 1500, }, { '4.devname': 'veth0', @@ -451,6 +487,5 @@ def test_more_interfaces(self): '5.status': 'enabled', }, ] - - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqual(o.intermediate_data['netconf'], expected) From e29eacd09fd7d628012df44a66826d49e06185df Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 23 Jun 2017 10:37:06 +0200 Subject: [PATCH 105/342] fixed typo in interface keyword --- tests/airos/test_netconf.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index d84ac2502..ec2dffd0e 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -9,7 +9,7 @@ class TestNetconfConverter(unittest.TestCase): def test_netconf_key(self): o = self.backend({ - "intefaces": [] + 'interfaces': [] }) o.to_intermediate() @@ -24,7 +24,7 @@ def test_netconf_key(self): def test_active_interface(self): o = self.backend({ - "intefaces": [{ + 'interfaces': [{ 'name': 'eth0', 'type': 'ethernet', }] @@ -39,22 +39,23 @@ def test_active_interface(self): '1.up': 'enabled', '1.flowcontrol.tx.status': 'enabled', '1.flowcontrol.rx.status': 'enabled', - '1.autoip.status': 'disabled', }, { 'status': 'enabled', }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + i_want_the_truth(self, o.intermediate_data['netconf'], expected) def test_inactive_interface(self): o = self.backend({ - "intefaces": [{ - 'name': 'eth0', - 'type': 'ethernet', - 'disabled': True, - }] + 'interfaces': [ + { + 'name': 'eth0', + 'type': 'ethernet', + 'disabled': True, + } + ] }) o.to_intermediate() From 6b7c556349533ad2dcf40ed255636f9465b81501 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 23 Jun 2017 10:37:31 +0200 Subject: [PATCH 106/342] fixed wrong indentation for tests in netconf --- tests/airos/test_netconf.py | 784 ++++++++++++++++++------------------ 1 file changed, 392 insertions(+), 392 deletions(-) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index ec2dffd0e..5e7876ca2 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -77,416 +77,416 @@ def test_inactive_interface(self): self.assertEqual(o.intermediate_data['netconf'], expected) - def test_vlan(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'eth0.1', - 'type': 'ethernet', - } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'eth0.1', - '1.status': 'enabled', - '1.up': 'enabled', - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, - ] + def test_vlan(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'eth0.1', + 'type': 'ethernet', + } + ], + }) - self.assertEqual(o.intermediate_data['netconf'], expected) + o.to_intermediate() - def test_management_vlan(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'eth0.1', - 'type': 'ethernet', - } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'eth0.1', - '1.status': 'enabled', - '1.up': 'enabled', - '1.autoip.status': 'disabled', - '1.role': 'mlan', - }, - { - 'status': 'enabled', - }, - ] + expected = [ + { + '1.devname': 'eth0.1', + '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] - self.assertEqual(o.intermediate_data['netconf'], expected) - - def test_access_point(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'wlan0', - 'type': 'wireless', - 'wireless': { - 'radio': 'ath0', - 'mode': 'access_point', - 'ssid': 'ap-ssid-example', - } - } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'ath0', - '1.mtu': 1500, - '1.status': 'enabled', - '1.up': 'enabled', - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, - ] + self.assertEqual(o.intermediate_data['netconf'], expected) - self.assertEqual(o.intermediate_data['netconf'], expected) - - def test_station(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'wlan0', - 'type': 'wireless', - 'wireless': { - 'radio': 'ath0', - 'mode': 'station', - 'ssid': 'ap-ssid-example', - } - } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'ath0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.mtu': 1500, - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, - ] + def test_management_vlan(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'eth0.1', + 'type': 'ethernet', + } + ], + }) - self.assertEqual(o.intermediate_data['netconf'], expected) - - def test_adhoc(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'wlan0', - 'type': 'wireless', - 'wireless': { - 'radio': 'ath0', - 'mode': 'adhoc', - 'ssid': 'ap-ssid-example', - } - } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'ath0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.mtu': 1500, - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, - ] + o.to_intermediate() - self.assertEqual(o.intermediate_data['netconf'], expected) - - def test_wds(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'wlan0', - 'type': 'wireless', - 'wireless': { - 'radio': 'ath0', - 'mode': 'wds', - 'ssid': 'ap-ssid-example', - } - } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'ath0', - '1.mtu': 1500, - '1.up': 'enabled', - '1.status': 'enabled', - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, - ] + expected = [ + { + '1.devname': 'eth0.1', + '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', + '1.role': 'mlan', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) - self.assertEqual(o.intermediate_data['netconf'], expected) - - def test_monitor(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'wlan0', - 'type': 'wireless', - 'wireless': { - 'radio': 'ath0', - 'mode': 'monitor', - 'ssid': 'ap-ssid-example', - } + def test_access_point(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'access_point', + 'ssid': 'ap-ssid-example', } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'ath0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.autoip.status': 'enabled', - }, - { - 'status': 'enabled', - }, - ] - - self.assertEqual(o.intermediate_data['netconf'], expected) - - def test_80211s(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'wlan0', - 'type': 'wireless', - 'wireless': { - 'radio': 'ath0', - 'mode': '802.11s', - 'ssid': 'ap-ssid-example', - } + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'ath0', + '1.mtu': 1500, + '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_station(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'station', + 'ssid': 'ap-ssid-example', } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'ath0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, - ] - - self.assertEqual(o.intermediate_data['netconf'], expected) - - def test_bridge(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'br0', - 'type': 'bridge', - 'bridge_members': [ - 'eth0', - 'eth1', - ], + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'ath0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_adhoc(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'adhoc', + 'ssid': 'ap-ssid-example', } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'br0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.mtu': 1500, - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, - ] + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'ath0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqual(o.intermediate_data['netconf'], expected) - def test_virtual(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'veth0', - 'type': 'virtual', + def test_wds(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'wds', + 'ssid': 'ap-ssid-example', } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'veth0', - '1.status': 'enabled', - }, - { - 'status': 'enabled', - }, - ] + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'ath0', + '1.mtu': 1500, + '1.up': 'enabled', + '1.status': 'enabled', + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqual(o.intermediate_data['netconf'], expected) - def test_loopback(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'lopp0', - 'type': 'loopback', + def test_monitor(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'monitor', + 'ssid': 'ap-ssid-example', } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'loop0', - '1.status': 'enabled', - }, - { - 'status': 'enabled', - }, - ] + } + ], + }) - self.assertEqual(o.intermediate_data['netconf'], expected) + o.to_intermediate() - def test_other(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'fancyname0', - 'type': 'other', + expected = [ + { + '1.devname': 'ath0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_80211s(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': '802.11s', + 'ssid': 'ap-ssid-example', } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'fancyname0', - '1.status': 'enabled', - }, - { - 'status': 'enabled', - }, - ] - - self.assertEqual(o.intermediate_data['netconf'], expected) - - def test_more_interfaces(self): - o = self.backend({ - 'interfaces': [ - { - 'name': 'eth0', - 'type': 'ethernet', - }, - { - 'name': 'wlan0', - 'type': 'wireless', - 'wireless': { - 'radio': 'ath0', - 'mode': 'station', - 'ssid': 'ap-ssid-example', - } - }, - { - 'name': 'br0', - 'type': 'bridge', - 'bridge_members': [ - 'eth0', - 'wlan0', - ], - }, - { - 'name': 'veth0', - 'type': 'virtual', - }, - { - 'name': 'loop0', - 'type': 'loopback', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'ath0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_bridge(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'br0', + 'type': 'bridge', + 'bridge_members': [ + 'eth0', + 'eth1', + ], + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'br0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_virtual(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'veth0', + 'type': 'virtual', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'veth0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_loopback(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'lopp0', + 'type': 'loopback', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'loop0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_other(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'fancyname0', + 'type': 'other', + } + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'fancyname0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) + + def test_more_interfaces(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'eth0', + 'type': 'ethernet', + }, + { + 'name': 'wlan0', + 'type': 'wireless', + 'wireless': { + 'radio': 'ath0', + 'mode': 'station', + 'ssid': 'ap-ssid-example', } - ], - }) - - o.to_intermediate() - - expected = [ - { - '1.devname': 'eth0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.mtu': 1500, - }, - { - '2.devname': 'ath0', - '2.status': 'enabled', - '2.up': 'enabled', - '3.mtu': 1500, - }, - { - '3.devname': 'br0', - '3.status': 'enabled', - '3.up': 'enabled', - '3.mtu': 1500, - }, - { - '4.devname': 'veth0', - '4.status': 'enabled', - }, - { - '5.devname': 'loop0', - '5.status': 'enabled', - }, - ] + }, + { + 'name': 'br0', + 'type': 'bridge', + 'bridge_members': [ + 'eth0', + 'wlan0', + ], + }, + { + 'name': 'veth0', + 'type': 'virtual', + }, + { + 'name': 'loop0', + 'type': 'loopback', + } + ], + }) + + o.to_intermediate() - self.assertEqual(o.intermediate_data['netconf'], expected) + expected = [ + { + '1.devname': 'eth0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + }, + { + '2.devname': 'ath0', + '2.status': 'enabled', + '2.up': 'enabled', + '3.mtu': 1500, + }, + { + '3.devname': 'br0', + '3.status': 'enabled', + '3.up': 'enabled', + '3.mtu': 1500, + }, + { + '4.devname': 'veth0', + '4.status': 'enabled', + }, + { + '5.devname': 'loop0', + '5.status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['netconf'], expected) From bc5c66fb0c3f4e28b250f5719bf8f2488f557d14 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 3 Jul 2017 17:51:31 +0200 Subject: [PATCH 107/342] [test] add test for wpasupplicant --- tests/airos/test_wpasupplicant.py | 194 ++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 tests/airos/test_wpasupplicant.py diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py new file mode 100644 index 000000000..3c1bb3200 --- /dev/null +++ b/tests/airos/test_wpasupplicant.py @@ -0,0 +1,194 @@ +import unittest + +from netjsonconfig.backends.airos.converters import * +from netjsonconfig.exceptions import ValidationError + +from .dummy import WpasupplicantAirOS + + +class TestWpasupplicantConverter(unittest.TestCase): + + backend = WpasupplicantAirOS + + maxDiff = 2000 + + def test_invalid_encryption(self): + + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "autostart": True, + "wireless": { + "radio": "radio0", + "mode": "access_point", + "ssid": "ap-ssid-example", + }, + "encryption": { + "protocol": "wep", + }, + } + ] + }) + with self.assertRaises(ValidationError): + o.validate() + + def test_no_encryption(self): + + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "autostart": True, + "wireless": { + "radio": "radio0", + "mode": "access_point", + "ssid": "ap-ssid-example", + }, + "encryption": { + "protocol": "none", + }, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'disabled', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.phase2=auth': 'MSCHAPV2', + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + { + 'status': 'enabled', + } + ] + + for (a, b) in zip(o.intermediate_data['wpasupplicant'], expected): + self.assertEqual(a, b) + + def test_wpa2_personal(self): + + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "autostart": True, + "wireless": { + "radio": "radio0", + "mode": "access_point", + "ssid": "ap-ssid-example", + }, + "encryption": { + "protocol": "wpa2_personal", + "key": "cucumber", + }, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'device.1.driver': 'madwifi', + 'device.1.devname': 'radio0', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.phase2=auth': 'MSCHAPV2', + 'profile.1.network.1.eap.1.status': 'disabled', + 'profile.1.network.1.psk': 'cucumber', + 'profile.1.network.1.pairwise.1.name': 'CCMP', + 'profile.1.network.1.proto.1.name': 'RSN', + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.key_mgmt.1.name': 'WPA-PSK', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + { + 'status': 'enabled', + } + ] + + for (a, b) in zip(o.intermediate_data['wpasupplicant'], expected): + self.assertEqual(a, b) + + def test_wpa2_enterprise(self): + + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "autostart": True, + "wireless": { + "radio": "radio0", + "mode": "access_point", + "ssid": "ap-ssid-example", + }, + "encryption": { + "protocol": "wpa2_enterprise", + "key": "cucumber", + }, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'device.1.driver': 'madwifi', + 'device.1.devname': 'radio0', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.phase2=auth': 'MSCHAPV2', + 'profile.1.network.1.eap.1.status': 'enabled', + 'profile.1.network.1.eap.1.name': 'TTLS', + 'profile.1.network.1.password': 'TODO', + 'profile.1.network.1.identity': 'TODO', + 'profile.1.network.1.anonymous_identity': 'TODO', + 'profile.1.network.1.psk': 'cucumber', + 'profile.1.network.1.pairwise.1.name': 'CCMP', + 'profile.1.network.1.proto.1.name': 'RSN', + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.key_mgmt.1.name': 'WPA-EAP', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + { + 'status': 'enabled', + } + ] + + for (a, b) in zip(o.intermediate_data['wpasupplicant'], expected): + self.assertEqual(a, b) From 1fedaa8211432b8e4710bd12c7583f98f09ebe99 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 12:29:08 +0200 Subject: [PATCH 108/342] [test] skip tests as not required by airos or target configuration --- tests/airos/test_netconf.py | 2 ++ tests/airos/test_wpasupplicant.py | 1 + 2 files changed, 3 insertions(+) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index 5e7876ca2..4e4776fa6 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -289,6 +289,8 @@ def test_monitor(self): self.assertEqual(o.intermediate_data['netconf'], expected) + + @unittest.skip("AirOS does not support 802.11s") def test_80211s(self): o = self.backend({ 'interfaces': [ diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 3c1bb3200..76a68a367 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -136,6 +136,7 @@ def test_wpa2_personal(self): for (a, b) in zip(o.intermediate_data['wpasupplicant'], expected): self.assertEqual(a, b) + @unittest.skip("target wpa2_enterprise later") def test_wpa2_enterprise(self): o = self.backend({ From be8e3979614297af909b933ac69c33d572320089 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 12:03:02 +0200 Subject: [PATCH 109/342] [test] create test case for checking converter outputs --- tests/airos/dummy.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/airos/dummy.py b/tests/airos/dummy.py index e08eb9239..7b0e90477 100644 --- a/tests/airos/dummy.py +++ b/tests/airos/dummy.py @@ -2,6 +2,33 @@ from netjsonconfig.backends.airos.converters import * +from unittest import TestCase + + +class ConverterTest(TestCase): + """ + Test case specific for intermediate configuration checks + + The intermediate configuration is a dict-like object with + section names as keys and a list of configuration values + as values + """ + maxDiff = 1000 + + def assertEqualConfig(self, a, b): + """ + Test that the content of two list is the equal + element wise + + This provides smaller, more specific, reports as it will trigger + failure for differently ordered elements or the element + content + + If an element fails the assertion will be the only one printed + """ + for (a, b) in zip(a, b): + self.assertEqual(a, b) + class AaaAirOS(AirOS): """ From fe0d9c0a74507d42ed7dab1feabd756478ad9c25 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 12:32:31 +0200 Subject: [PATCH 110/342] [test] use custom converter test class instead of unittest one --- tests/airos/test_netconf.py | 45 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index 4e4776fa6..4f0e85a9b 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import NetconfAirOS, ConverterTest -from .dummy import NetconfAirOS - -class TestNetconfConverter(unittest.TestCase): +class TestNetconfConverter(ConverterTest): backend = NetconfAirOS @@ -20,7 +18,7 @@ def test_netconf_key(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_active_interface(self): o = self.backend({ @@ -45,7 +43,7 @@ def test_active_interface(self): }, ] - i_want_the_truth(self, o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_inactive_interface(self): o = self.backend({ @@ -75,7 +73,7 @@ def test_inactive_interface(self): ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_vlan(self): o = self.backend({ @@ -101,7 +99,7 @@ def test_vlan(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_management_vlan(self): o = self.backend({ @@ -128,7 +126,7 @@ def test_management_vlan(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_access_point(self): o = self.backend({ @@ -160,7 +158,7 @@ def test_access_point(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_station(self): o = self.backend({ @@ -192,7 +190,7 @@ def test_station(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_adhoc(self): o = self.backend({ @@ -224,8 +222,9 @@ def test_adhoc(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) + @unittest.skip def test_wds(self): o = self.backend({ 'interfaces': [ @@ -256,7 +255,7 @@ def test_wds(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_monitor(self): o = self.backend({ @@ -286,9 +285,8 @@ def test_monitor(self): 'status': 'enabled', }, ] - - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) @unittest.skip("AirOS does not support 802.11s") def test_80211s(self): @@ -299,7 +297,8 @@ def test_80211s(self): 'type': 'wireless', 'wireless': { 'radio': 'ath0', - 'mode': '802.11s', + 'mode': 'access_point', + 'protocol': '802.11s', 'ssid': 'ap-ssid-example', } } @@ -319,8 +318,8 @@ def test_80211s(self): 'status': 'enabled', }, ] - - self.assertEqual(o.intermediate_data['netconf'], expected) + + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_bridge(self): o = self.backend({ @@ -351,7 +350,7 @@ def test_bridge(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_virtual(self): o = self.backend({ @@ -375,7 +374,7 @@ def test_virtual(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_loopback(self): o = self.backend({ @@ -399,7 +398,7 @@ def test_loopback(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_other(self): o = self.backend({ @@ -423,7 +422,7 @@ def test_other(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_more_interfaces(self): o = self.backend({ @@ -491,4 +490,4 @@ def test_more_interfaces(self): }, ] - self.assertEqual(o.intermediate_data['netconf'], expected) + self.assertEqualConfig(o.intermediate_data['netconf'], expected) From d47b20a99100fc97cdbf0af772ba4e798572f616 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 13:19:03 +0200 Subject: [PATCH 111/342] [test][netconf] skip unnecessary tests --- tests/airos/test_netconf.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index 4f0e85a9b..cb37d7f14 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -1,3 +1,4 @@ +from unittest import skip from .dummy import NetconfAirOS, ConverterTest @@ -192,6 +193,7 @@ def test_station(self): self.assertEqualConfig(o.intermediate_data['netconf'], expected) + @skip("Airos does not support ``adhoc`` mode") def test_adhoc(self): o = self.backend({ 'interfaces': [ @@ -224,7 +226,7 @@ def test_adhoc(self): self.assertEqualConfig(o.intermediate_data['netconf'], expected) - @unittest.skip + @skip("Airos does not support wds") def test_wds(self): o = self.backend({ 'interfaces': [ @@ -257,6 +259,7 @@ def test_wds(self): self.assertEqualConfig(o.intermediate_data['netconf'], expected) + @skip("Airos does not support ``monitor`` mode") def test_monitor(self): o = self.backend({ 'interfaces': [ @@ -288,7 +291,7 @@ def test_monitor(self): self.assertEqualConfig(o.intermediate_data['netconf'], expected) - @unittest.skip("AirOS does not support 802.11s") + @skip("AirOS does not support 802.11s") def test_80211s(self): o = self.backend({ 'interfaces': [ @@ -352,6 +355,7 @@ def test_bridge(self): self.assertEqualConfig(o.intermediate_data['netconf'], expected) + @skip("Airos does not support virtual interfaces") def test_virtual(self): o = self.backend({ 'interfaces': [ @@ -376,6 +380,7 @@ def test_virtual(self): self.assertEqualConfig(o.intermediate_data['netconf'], expected) + @skip("Airos does not support ``loopback`` interface") def test_loopback(self): o = self.backend({ 'interfaces': [ @@ -400,6 +405,7 @@ def test_loopback(self): self.assertEqualConfig(o.intermediate_data['netconf'], expected) + @skip("Airos does not support ``other`` interfaces") def test_other(self): o = self.backend({ 'interfaces': [ From e3dfb0d671beb70c9527a0f1ffe3b0ab9e0dc153 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 13:20:11 +0200 Subject: [PATCH 112/342] [test][netconf] update expected output to airos 8.3 --- tests/airos/test_netconf.py | 40 +++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index cb37d7f14..1fd46f958 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -33,11 +33,14 @@ def test_active_interface(self): expected = [ { + '1.autoip.status': 'disabled', + '1.autoneg': 'enabled', '1.devname': 'eth0', - '1.status': 'enabled', - '1.up': 'enabled', '1.flowcontrol.tx.status': 'enabled', '1.flowcontrol.rx.status': 'enabled', + '1.mtu': 1500, + '1.status': 'enabled', + '1.up': 'enabled', }, { 'status': 'enabled', @@ -61,12 +64,14 @@ def test_inactive_interface(self): expected = [ { + '1.autoip.status': 'disabled', + '1.autoneg': 'enabled', '1.devname': 'eth0', - '1.status': 'enabled', - '1.up': 'disabled', '1.flowcontrol.tx.status': 'enabled', '1.flowcontrol.rx.status': 'enabled', - '1.autoip.status': 'disabled', + '1.mtu': 1500, + '1.status': 'enabled', + '1.up': 'disabled', }, { 'status': 'enabled', @@ -91,6 +96,7 @@ def test_vlan(self): expected = [ { '1.devname': 'eth0.1', + '1.mtu': 1500, '1.status': 'enabled', '1.up': 'enabled', '1.autoip.status': 'disabled', @@ -117,10 +123,12 @@ def test_management_vlan(self): expected = [ { '1.devname': 'eth0.1', + '1.ip': '192.168.1.20', + '1.netmask': '255.255.255.0', + '1.mtu': 1500, + '1.role': 'mlan', '1.status': 'enabled', '1.up': 'enabled', - '1.autoip.status': 'disabled', - '1.role': 'mlan', }, { 'status': 'enabled', @@ -469,30 +477,42 @@ def test_more_interfaces(self): expected = [ { + '1.autoip.status': 'disabled', + '1.autoneg': 'enabled', '1.devname': 'eth0', + '1.flowcontrol.rx.status': 'enabled', + '1.flowcontrol.tx.status': 'enabled', + '1.mtu': 1500, '1.status': 'enabled', '1.up': 'enabled', - '1.mtu': 1500, }, { + '2.autoip.status': 'disabled', '2.devname': 'ath0', + '2.mtu': 1500, '2.status': 'enabled', '2.up': 'enabled', - '3.mtu': 1500, }, { + '3.autoip.status': 'disabled', '3.devname': 'br0', + '3.mtu': 1500, '3.status': 'enabled', '3.up': 'enabled', - '3.mtu': 1500, }, { + '4.autoip.status': 'disabled', '4.devname': 'veth0', + '4.mtu': 1500, '4.status': 'enabled', + '4.up': 'enabled', }, { + '5.autoip.status': 'disabled', '5.devname': 'loop0', + '5.mtu': 1500, '5.status': 'enabled', + '5.up': 'enabled', }, ] From 02a86c70949a1230d6ff9d5d45a65c605bd55d42 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 13:20:40 +0200 Subject: [PATCH 113/342] [test][netconf] fix validation error in netconf tests --- tests/airos/test_netconf.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index 1fd46f958..1dc0cbbd6 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -114,6 +114,15 @@ def test_management_vlan(self): { 'name': 'eth0.1', 'type': 'ethernet', + 'addresses': [ + { + 'address': '192.168.1.20', + 'family': 'ipv4', + 'management': True, + 'mask': 24, + 'proto': 'static', + } + ] } ], }) @@ -178,6 +187,7 @@ def test_station(self): 'wireless': { 'radio': 'ath0', 'mode': 'station', + 'bssid': '00:11:22:33:44:55', 'ssid': 'ap-ssid-example', } } @@ -451,6 +461,7 @@ def test_more_interfaces(self): 'wireless': { 'radio': 'ath0', 'mode': 'station', + 'bssid': '00:11:22:33:44:55', 'ssid': 'ap-ssid-example', } }, From addd5d53abb010f35abaf4782c31fa03d3cabade Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 13:21:29 +0200 Subject: [PATCH 114/342] [test][wireless] update expected result to airos 8.3 --- tests/airos/test_wireless.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index cf73eb770..a6d1a6b6c 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -42,7 +42,7 @@ def test_active_wireless(self): expected = [ { '1.addmtikie': 'enabled', - '1.devname': 'wlan0', + '1.devname': 'radio0', '1.hide_ssid': 'disabled', '1.security.type': 'none', '1.signal_led1': 75, @@ -95,7 +95,7 @@ def test_inactive_wireless(self): expected = [ { '1.addmtikie': 'enabled', - '1.devname': 'wlan0', + '1.devname': 'radio0', '1.hide_ssid': 'disabled', '1.security.type': 'none', '1.signal_led1': 75, From 99ea564e11b906927d752d015b0258f9d33d8f52 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 13:22:28 +0200 Subject: [PATCH 115/342] [test][wireless] use ConverterTest instead of naive --- tests/airos/test_wireless.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index a6d1a6b6c..088ea2e4e 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -1,11 +1,9 @@ import unittest -from netjsonconfig.backends.airos.converters import * +from .dummy import WirelessAirOS, ConverterTest -from .dummy import WirelessAirOS - -class TestWirelessConverter(unittest.TestCase): +class TestWirelessConverter(ConverterTest): backend = WirelessAirOS @@ -59,7 +57,7 @@ def test_active_wireless(self): } ] - self.assertEqual(o.intermediate_data['wireless'], expected) + self.assertEqualConfig(o.intermediate_data['wireless'], expected) def test_inactive_wireless(self): @@ -112,4 +110,4 @@ def test_inactive_wireless(self): } ] - self.assertEqual(o.intermediate_data['wireless'], expected) + self.assertEqualConfig(o.intermediate_data['wireless'], expected) From c56f46198f0c60f76d91fcf30e41eb5e057b948b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 13:23:24 +0200 Subject: [PATCH 116/342] [test][wpasupplicant] use ConverterTest instead of naive --- tests/airos/test_wpasupplicant.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 76a68a367..759af35cc 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -1,12 +1,11 @@ import unittest -from netjsonconfig.backends.airos.converters import * from netjsonconfig.exceptions import ValidationError -from .dummy import WpasupplicantAirOS +from .dummy import WpasupplicantAirOS, ConverterTest -class TestWpasupplicantConverter(unittest.TestCase): +class TestWpasupplicantConverter(ConverterTest): backend = WpasupplicantAirOS From b59bde6990b39047a7ed0ea310a94ee69dc76565 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 6 Jul 2017 13:23:57 +0200 Subject: [PATCH 117/342] [test][wpasupplicant] skip wpa2_enterprise test as it is a later target --- tests/airos/test_wpasupplicant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 759af35cc..f89bcfece 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -1,4 +1,4 @@ -import unittest +from unittest import skip from netjsonconfig.exceptions import ValidationError From 7f26ca172320b5e4e5ab0e47f12ec76a0084c18b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 7 Jul 2017 10:06:26 +0200 Subject: [PATCH 118/342] remove redundant test file --- tests/airos/test_system.py | 51 -------------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 tests/airos/test_system.py diff --git a/tests/airos/test_system.py b/tests/airos/test_system.py deleted file mode 100644 index 13299fbb8..000000000 --- a/tests/airos/test_system.py +++ /dev/null @@ -1,51 +0,0 @@ -import unittest - -from .dummy import ResolvAirOS - - -class TestResolvConverter(unittest.TestCase): - - backend = ResolvAirOS - - def test_resolv(self): - o = self.backend({ - "dns_servers": [ - "10.150.42.1" - ], - }) - - o.to_intermediate() - - expected = [ - { - 'host.1.name' : '', - }, - { - 'nameserver.1.ip' : '10.150.42.1', - 'nameserver.1.status' : 'enabled', - }, - { - 'status' : 'enabled', - }, - ] - - - self.assertEqual(o.intermediate_data['resolv'], expected) - - def test_no_dns_server(self): - o = self.backend({ - "dns_servers": [], - }) - - o.to_intermediate() - - expected = [ - { - 'host.1.name' : '', - }, - { - 'status' : 'enabled', - }, - ] - - self.assertEqual(o.intermediate_data['resolv'], expected) From ae2de5f281c31271597bc0fa208a2adeb9b8e66f Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 7 Jul 2017 10:06:59 +0200 Subject: [PATCH 119/342] update test default for resolv converter --- tests/airos/test_resolv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/airos/test_resolv.py b/tests/airos/test_resolv.py index 13299fbb8..9597e4a15 100644 --- a/tests/airos/test_resolv.py +++ b/tests/airos/test_resolv.py @@ -18,7 +18,7 @@ def test_resolv(self): expected = [ { - 'host.1.name' : '', + 'host.1.name' : 'airos', }, { 'nameserver.1.ip' : '10.150.42.1', @@ -41,7 +41,7 @@ def test_no_dns_server(self): expected = [ { - 'host.1.name' : '', + 'host.1.name' : 'airos', }, { 'status' : 'enabled', From 2db796354d185faa2e177025bd616949e15ac324 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 7 Jul 2017 10:07:23 +0200 Subject: [PATCH 120/342] fix importerror in decorator --- tests/airos/test_wpasupplicant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index f89bcfece..c0e7e9bbf 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -135,7 +135,7 @@ def test_wpa2_personal(self): for (a, b) in zip(o.intermediate_data['wpasupplicant'], expected): self.assertEqual(a, b) - @unittest.skip("target wpa2_enterprise later") + @skip("target wpa2_enterprise later") def test_wpa2_enterprise(self): o = self.backend({ From 80427d484c555a13231958be5bb277fbc53583a6 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 7 Jul 2017 10:24:24 +0200 Subject: [PATCH 121/342] fixed test input and expected output --- tests/airos/test_gui.py | 2 +- tests/airos/test_resolv.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/airos/test_gui.py b/tests/airos/test_gui.py index 1e224ce80..6aa9844ce 100644 --- a/tests/airos/test_gui.py +++ b/tests/airos/test_gui.py @@ -9,7 +9,7 @@ class TestGuiConverter(unittest.TestCase): def test_gui_key(self): o = self.backend({ - "general": {} + 'gui': {}, }) o.to_intermediate() diff --git a/tests/airos/test_resolv.py b/tests/airos/test_resolv.py index 9597e4a15..b3132305b 100644 --- a/tests/airos/test_resolv.py +++ b/tests/airos/test_resolv.py @@ -19,6 +19,7 @@ def test_resolv(self): expected = [ { 'host.1.name' : 'airos', + 'host.1.status': 'enabled', }, { 'nameserver.1.ip' : '10.150.42.1', @@ -42,6 +43,7 @@ def test_no_dns_server(self): expected = [ { 'host.1.name' : 'airos', + 'host.1.status': 'enabled', }, { 'status' : 'enabled', From 8f3c2da7b3f10bf2195268ba387cc44619d17368 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 7 Jul 2017 12:33:05 +0200 Subject: [PATCH 122/342] [test] updated wireless test output --- tests/airos/test_wireless.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index 088ea2e4e..118a3cd78 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -42,6 +42,12 @@ def test_active_wireless(self): '1.addmtikie': 'enabled', '1.devname': 'radio0', '1.hide_ssid': 'disabled', + '1.l2_isolation': 'disabled', + '1.mac_acl.policy': 'allow', + '1.mac_acl.status': 'disabled', + '1.mcast.enhance': 0, + '1.rate.auto': 'enabled', + '1.rate.mcs': -1, '1.security.type': 'none', '1.signal_led1': 75, '1.signal_led2': 50, @@ -51,6 +57,7 @@ def test_active_wireless(self): '1.ssid': 'ap-ssid-example', '1.status': 'enabled', '1.wds.status': 'enabled', + }, { 'status': 'enabled', @@ -95,6 +102,12 @@ def test_inactive_wireless(self): '1.addmtikie': 'enabled', '1.devname': 'radio0', '1.hide_ssid': 'disabled', + '1.l2_isolation': 'disabled', + '1.mac_acl.policy': 'allow', + '1.mac_acl.status': 'disabled', + '1.mcast.enhance': 0, + '1.rate.auto': 'enabled', + '1.rate.mcs': -1, '1.security.type': 'none', '1.signal_led1': 75, '1.signal_led2': 50, From 7fc58394b5977db624a86a0c7f4d03da565fcc50 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 10 Jul 2017 12:35:37 +0200 Subject: [PATCH 123/342] [test] updated ntp test for converter --- tests/airos/test_ntp.py | 63 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/tests/airos/test_ntp.py b/tests/airos/test_ntp.py index 408953904..51f451f2b 100644 --- a/tests/airos/test_ntp.py +++ b/tests/airos/test_ntp.py @@ -9,7 +9,7 @@ class TestResolvConverter(unittest.TestCase): def test_ntp_key(self): o = self.backend({ - "general": {} + "ntp_servers": [], }) o.to_intermediate() @@ -20,5 +20,66 @@ def test_ntp_key(self): }, ] + self.assertEqual(o.intermediate_data['ntpclient'], expected) + + def test_no_ntp_server(self): + o = self.backend({ + "ntp_servers": [], + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'disabled', + }, + ] + + self.assertEqual(o.intermediate_data['ntpclient'], expected) + + def test_single_ntp_server(self): + o = self.backend({ + "ntp_servers": [ + '0.openwrt.pool.ntp.org', + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.server': '0.openwrt.pool.ntp.org', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqual(o.intermediate_data['ntpclient'], expected) + + def test_multiple_ntp_server(self): + o = self.backend({ + "ntp_servers": [ + '0.openwrt.pool.ntp.org', + '1.openwrt.pool.ntp.org', + ], + }) + + o.to_intermediate() + + expected = [ + { + '1.server': '0.openwrt.pool.ntp.org', + '1.status': 'enabled', + }, + { + '2.server': '1.openwrt.pool.ntp.org', + '2.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] self.assertEqual(o.intermediate_data['ntpclient'], expected) From bd8fa0f3dd9b565148dccdfe5a28287372f2d984 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 10 Jul 2017 15:13:31 +0200 Subject: [PATCH 124/342] [test] changed converter tests to use the custom test class --- tests/airos/test_aaa.py | 9 +++------ tests/airos/test_bridge.py | 14 ++++++-------- tests/airos/test_discovery.py | 8 +++----- tests/airos/test_dyndns.py | 9 +++------ tests/airos/test_gui.py | 9 +++------ tests/airos/test_httpd.py | 9 +++------ tests/airos/test_ntp.py | 14 ++++++-------- tests/airos/test_pwdog.py | 8 +++----- tests/airos/test_radio.py | 12 +++++------- tests/airos/test_resolv.py | 11 ++++------- tests/airos/test_vlan.py | 20 ++++++++------------ tests/airos/test_wireless.py | 2 -- 12 files changed, 47 insertions(+), 78 deletions(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index f9e646be3..fc2edf699 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import AaaAirOS, ConverterTest -from .dummy import AaaAirOS - -class TestResolvConverter(unittest.TestCase): +class TestResolvConverter(ConverterTest): backend = AaaAirOS @@ -20,5 +18,4 @@ def test_aaa_key(self): }, ] - - self.assertEqual(o.intermediate_data['aaa'], expected) + self.assertEqualConfig(o.intermediate_data['aaa'], expected) diff --git a/tests/airos/test_bridge.py b/tests/airos/test_bridge.py index 8423b2730..61b6c9cc1 100644 --- a/tests/airos/test_bridge.py +++ b/tests/airos/test_bridge.py @@ -1,11 +1,9 @@ -import unittest - from netjsonconfig.backends.airos.converters import * -from .dummy import BridgeAirOS +from .dummy import BridgeAirOS, ConverterTest -class TestBridgeConverter(unittest.TestCase): +class TestBridgeConverter(ConverterTest): """ tests for backends.airos.renderers.SystemRenderer """ @@ -55,7 +53,7 @@ def test_active_bridge(self): } ] - self.assertEqual(o.intermediate_data['bridge'], expected) + self.assertEqualConfig(o.intermediate_data['bridge'], expected) def test_disabled_bridge(self): o = self.backend({ @@ -100,7 +98,7 @@ def test_disabled_bridge(self): } ] - self.assertEqual(o.intermediate_data['bridge'], expected) + self.assertEqualConfig(o.intermediate_data['bridge'], expected) def test_many_bridges(self): o = self.backend({ @@ -174,7 +172,7 @@ def test_many_bridges(self): } ] - self.assertEqual(o.intermediate_data['bridge'], expected) + self.assertEqualConfig(o.intermediate_data['bridge'], expected) def test_no_bridge(self): o = self.backend({ @@ -200,4 +198,4 @@ def test_no_bridge(self): } ] - self.assertEqual(o.intermediate_data['bridge'], expected) + self.assertEqualConfig(o.intermediate_data['bridge'], expected) diff --git a/tests/airos/test_discovery.py b/tests/airos/test_discovery.py index efa82dc53..b759ff75b 100644 --- a/tests/airos/test_discovery.py +++ b/tests/airos/test_discovery.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import DiscoveryAirOS, ConverterTest -from .dummy import DiscoveryAirOS - -class TestDiscoveryConverter(unittest.TestCase): +class TestDiscoveryConverter(ConverterTest): backend = DiscoveryAirOS @@ -22,4 +20,4 @@ def test_discovery_key(self): ] - self.assertEqual(o.intermediate_data['discovery'], expected) + self.assertEqualConfig(o.intermediate_data['discovery'], expected) diff --git a/tests/airos/test_dyndns.py b/tests/airos/test_dyndns.py index 4195c2554..2c8148d5f 100644 --- a/tests/airos/test_dyndns.py +++ b/tests/airos/test_dyndns.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import DyndnsAirOS, ConverterTest -from .dummy import DyndnsAirOS - -class TestDyndnsConverter(unittest.TestCase): +class TestDyndnsConverter(ConverterTest): backend = DyndnsAirOS @@ -20,5 +18,4 @@ def test_Dyndns_key(self): }, ] - - self.assertEqual(o.intermediate_data['dyndns'], expected) + self.assertEqualConfig(o.intermediate_data['dyndns'], expected) diff --git a/tests/airos/test_gui.py b/tests/airos/test_gui.py index 6aa9844ce..43c718eaf 100644 --- a/tests/airos/test_gui.py +++ b/tests/airos/test_gui.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import GuiAirOS, ConverterTest -from .dummy import GuiAirOS - -class TestGuiConverter(unittest.TestCase): +class TestGuiConverter(ConverterTest): backend = GuiAirOS @@ -23,5 +21,4 @@ def test_gui_key(self): }, ] - - self.assertEqual(o.intermediate_data['gui'], expected) + self.assertEqualConfig(o.intermediate_data['gui'], expected) diff --git a/tests/airos/test_httpd.py b/tests/airos/test_httpd.py index b094f217f..4e0b6696e 100644 --- a/tests/airos/test_httpd.py +++ b/tests/airos/test_httpd.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import HttpdAirOS, ConverterTest -from .dummy import HttpdAirOS - -class TestHttpdConverter(unittest.TestCase): +class TestHttpdConverter(ConverterTest): backend = HttpdAirOS @@ -26,5 +24,4 @@ def test_httpd_key(self): }, ] - - self.assertEqual(o.intermediate_data['httpd'], expected) + self.assertEqualConfig(o.intermediate_data['httpd'], expected) diff --git a/tests/airos/test_ntp.py b/tests/airos/test_ntp.py index 51f451f2b..433d07200 100644 --- a/tests/airos/test_ntp.py +++ b/tests/airos/test_ntp.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import NtpclientAirOS, ConverterTest -from .dummy import NtpclientAirOS - -class TestResolvConverter(unittest.TestCase): +class TestResolvConverter(ConverterTest): backend = NtpclientAirOS @@ -20,7 +18,7 @@ def test_ntp_key(self): }, ] - self.assertEqual(o.intermediate_data['ntpclient'], expected) + self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) def test_no_ntp_server(self): o = self.backend({ @@ -35,7 +33,7 @@ def test_no_ntp_server(self): }, ] - self.assertEqual(o.intermediate_data['ntpclient'], expected) + self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) def test_single_ntp_server(self): o = self.backend({ @@ -56,7 +54,7 @@ def test_single_ntp_server(self): }, ] - self.assertEqual(o.intermediate_data['ntpclient'], expected) + self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) def test_multiple_ntp_server(self): o = self.backend({ @@ -82,4 +80,4 @@ def test_multiple_ntp_server(self): }, ] - self.assertEqual(o.intermediate_data['ntpclient'], expected) + self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) diff --git a/tests/airos/test_pwdog.py b/tests/airos/test_pwdog.py index a57854d0a..0002b8cdf 100644 --- a/tests/airos/test_pwdog.py +++ b/tests/airos/test_pwdog.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import PwdogAirOS, ConverterTest -from .dummy import PwdogAirOS - -class TestPwdogConverter(unittest.TestCase): +class TestPwdogConverter(ConverterTest): backend = PwdogAirOS @@ -23,4 +21,4 @@ def test_ntp_key(self): }, ] - self.assertEqual(o.intermediate_data['pwdog'], expected) + self.assertEqualConfig(o.intermediate_data['pwdog'], expected) diff --git a/tests/airos/test_radio.py b/tests/airos/test_radio.py index c80f00c31..fd62570a7 100644 --- a/tests/airos/test_radio.py +++ b/tests/airos/test_radio.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import RadioAirOS, ConverterTest -from .dummy import RadioAirOS - -class TestRadioConverter(unittest.TestCase): +class TestRadioConverter(ConverterTest): backend = RadioAirOS @@ -20,7 +18,7 @@ def test_no_radio(self): }, ] - self.assertEqual(o.intermediate_data['radio'], expected) + self.assertEqualConfig(o.intermediate_data['radio'], expected) def test_active_radio(self): o = self.backend({ @@ -49,7 +47,7 @@ def test_active_radio(self): }, ] - self.assertEqual(o.intermediate_data['radio'], expected) + self.assertEqualConfig(o.intermediate_data['radio'], expected) def test_inactive_radio(self): o = self.backend({ @@ -78,4 +76,4 @@ def test_inactive_radio(self): }, ] - self.assertEqual(o.intermediate_data['radio'], expected) + self.assertEqualConfig(o.intermediate_data['radio'], expected) diff --git a/tests/airos/test_resolv.py b/tests/airos/test_resolv.py index b3132305b..849431568 100644 --- a/tests/airos/test_resolv.py +++ b/tests/airos/test_resolv.py @@ -1,9 +1,7 @@ -import unittest +from .dummy import ResolvAirOS, ConverterTest -from .dummy import ResolvAirOS - -class TestResolvConverter(unittest.TestCase): +class TestResolvConverter(ConverterTest): backend = ResolvAirOS @@ -30,8 +28,7 @@ def test_resolv(self): }, ] - - self.assertEqual(o.intermediate_data['resolv'], expected) + self.assertEqualConfig(o.intermediate_data['resolv'], expected) def test_no_dns_server(self): o = self.backend({ @@ -50,4 +47,4 @@ def test_no_dns_server(self): }, ] - self.assertEqual(o.intermediate_data['resolv'], expected) + self.assertEqualConfig(o.intermediate_data['resolv'], expected) diff --git a/tests/airos/test_vlan.py b/tests/airos/test_vlan.py index 6674c7524..353a0a011 100644 --- a/tests/airos/test_vlan.py +++ b/tests/airos/test_vlan.py @@ -1,11 +1,7 @@ -import unittest +from .dummy import VlanAirOS, ConverterTest -from netjsonconfig.backends.airos.converters import * -from .dummy import VlanAirOS - - -class TestVlanConverter(unittest.TestCase): +class TestVlanConverter(ConverterTest): """ tests for backends.airos.renderers.SystemRenderer """ @@ -37,7 +33,7 @@ def test_active_vlan(self): } ] - self.assertEqual(o.intermediate_data['vlan'], expected) + self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_disabled_vlan(self): @@ -65,7 +61,7 @@ def test_disabled_vlan(self): o.to_intermediate() - self.assertEqual(o.intermediate_data['vlan'], expected) + self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_many_vlan(self): @@ -104,7 +100,7 @@ def test_many_vlan(self): o.to_intermediate() - self.assertEqual(o.intermediate_data['vlan'], expected) + self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_mixed_vlan(self): @@ -143,7 +139,7 @@ def test_mixed_vlan(self): o.to_intermediate() - self.assertEqual(o.intermediate_data['vlan'], expected) + self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_no_vlan(self): @@ -165,7 +161,7 @@ def test_no_vlan(self): o.to_intermediate() - self.assertEqual(o.intermediate_data['vlan'], expected) + self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_one_vlan(self): @@ -199,4 +195,4 @@ def test_one_vlan(self): o.to_intermediate() - self.assertEqual(o.intermediate_data['vlan'], expected) + self.assertEqualConfig(o.intermediate_data['vlan'], expected) diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index 118a3cd78..12663234f 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -1,5 +1,3 @@ -import unittest - from .dummy import WirelessAirOS, ConverterTest From 00affc4f77dad263284e3e625a62b7265ca66110 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 10 Jul 2017 15:14:58 +0200 Subject: [PATCH 125/342] fixed syntastic errors --- tests/airos/test_aaa.py | 3 ++- tests/airos/test_resolv.py | 12 ++++++------ tests/airos/test_vlan.py | 14 +++++++------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index fc2edf699..c5d4db7b0 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -7,7 +7,8 @@ class TestResolvConverter(ConverterTest): def test_aaa_key(self): o = self.backend({ - "general": {} + "general": {}, + "interfaces": [], }) o.to_intermediate() diff --git a/tests/airos/test_resolv.py b/tests/airos/test_resolv.py index 849431568..3e1ee86a6 100644 --- a/tests/airos/test_resolv.py +++ b/tests/airos/test_resolv.py @@ -16,15 +16,15 @@ def test_resolv(self): expected = [ { - 'host.1.name' : 'airos', + 'host.1.name': 'airos', 'host.1.status': 'enabled', }, { - 'nameserver.1.ip' : '10.150.42.1', - 'nameserver.1.status' : 'enabled', + 'nameserver.1.ip': '10.150.42.1', + 'nameserver.1.status': 'enabled', }, { - 'status' : 'enabled', + 'status': 'enabled', }, ] @@ -39,11 +39,11 @@ def test_no_dns_server(self): expected = [ { - 'host.1.name' : 'airos', + 'host.1.name': 'airos', 'host.1.status': 'enabled', }, { - 'status' : 'enabled', + 'status': 'enabled', }, ] diff --git a/tests/airos/test_vlan.py b/tests/airos/test_vlan.py index 353a0a011..935bf0003 100644 --- a/tests/airos/test_vlan.py +++ b/tests/airos/test_vlan.py @@ -26,10 +26,10 @@ def test_active_vlan(self): '1.comment': '', '1.devname': 'eth0', '1.id': '1', - '1.status' : 'enabled', + '1.status': 'enabled', }, { - 'status' : 'enabled', + 'status': 'enabled', } ] @@ -55,7 +55,7 @@ def test_disabled_vlan(self): '1.status': 'disabled', }, { - 'status' : 'enabled', + 'status': 'enabled', } ] @@ -94,7 +94,7 @@ def test_many_vlan(self): '2.status': 'enabled', }, { - 'status' : 'enabled', + 'status': 'enabled', } ] @@ -133,7 +133,7 @@ def test_mixed_vlan(self): '2.status': 'enabled', }, { - 'status' : 'enabled', + 'status': 'enabled', } ] @@ -155,7 +155,7 @@ def test_no_vlan(self): expected = [ { - 'status' : 'enabled', + 'status': 'enabled', }, ] @@ -189,7 +189,7 @@ def test_one_vlan(self): '1.status': 'enabled', }, { - 'status' : 'enabled', + 'status': 'enabled', }, ] From 575653ff47076a8e8ba71d12874148e5e9a217ff Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 10 Jul 2017 18:03:36 +0200 Subject: [PATCH 126/342] [wpasupplicant] split test in station mode and access point mode --- tests/airos/test_wpasupplicant.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index c0e7e9bbf..c549a3870 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -5,12 +5,13 @@ from .dummy import WpasupplicantAirOS, ConverterTest -class TestWpasupplicantConverter(ConverterTest): - +class TestWpasupplicantStation(ConverterTest): + """ + Test the wpasupplicant converter for a + device in ``station`` mode + """ backend = WpasupplicantAirOS - maxDiff = 2000 - def test_invalid_encryption(self): o = self.backend({ @@ -79,8 +80,7 @@ def test_no_encryption(self): } ] - for (a, b) in zip(o.intermediate_data['wpasupplicant'], expected): - self.assertEqual(a, b) + self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) def test_wpa2_personal(self): @@ -132,8 +132,7 @@ def test_wpa2_personal(self): } ] - for (a, b) in zip(o.intermediate_data['wpasupplicant'], expected): - self.assertEqual(a, b) + self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) @skip("target wpa2_enterprise later") def test_wpa2_enterprise(self): @@ -190,5 +189,4 @@ def test_wpa2_enterprise(self): } ] - for (a, b) in zip(o.intermediate_data['wpasupplicant'], expected): - self.assertEqual(a, b) + self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) From f346ceff60ac2dff893346bbf517f444068fba10 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 10 Jul 2017 18:06:54 +0200 Subject: [PATCH 127/342] [wpasupplicant] add test for access point mode, change first test to station mode --- tests/airos/test_wpasupplicant.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index c549a3870..b1f262c51 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -25,7 +25,7 @@ def test_invalid_encryption(self): "autostart": True, "wireless": { "radio": "radio0", - "mode": "access_point", + "mode": "station", "ssid": "ap-ssid-example", }, "encryption": { @@ -50,8 +50,9 @@ def test_no_encryption(self): "autostart": True, "wireless": { "radio": "radio0", - "mode": "access_point", + "mode": "station", "ssid": "ap-ssid-example", + "bssid": "00:11:22:33:44:55", }, "encryption": { "protocol": "none", @@ -95,8 +96,9 @@ def test_wpa2_personal(self): "autostart": True, "wireless": { "radio": "radio0", - "mode": "access_point", + "mode": "station", "ssid": "ap-ssid-example", + "bssid": "00:11:22:33:44:55", }, "encryption": { "protocol": "wpa2_personal", @@ -148,7 +150,7 @@ def test_wpa2_enterprise(self): "autostart": True, "wireless": { "radio": "radio0", - "mode": "access_point", + "mode": "station", "ssid": "ap-ssid-example", }, "encryption": { @@ -190,3 +192,12 @@ def test_wpa2_enterprise(self): ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) + + +class TestWpasupplicantAccess(ConverterTest): + """ + Test the wpasupplicant converter for a + device in ``access_point`` mode + """ + + backend = WpasupplicantAirOS From 6f6fa25c83f89efc617672d55b8bd8720acd4b08 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 11 Jul 2017 11:34:47 +0200 Subject: [PATCH 128/342] added test for access_point mode without encryption --- tests/airos/test_wpasupplicant.py | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index b1f262c51..693089207 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -201,3 +201,43 @@ class TestWpasupplicantAccess(ConverterTest): """ backend = WpasupplicantAirOS + + def test_no_encryption(self): + + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "autostart": True, + "wireless": { + "radio": "radio0", + "mode": "access_point", + "ssid": "ap-ssid-example", + }, + "encryption": { + "protocol": "none", + }, + } + ] + }) + + o.to_intermediate() + + expected = [ + { + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + { + 'status': 'enabled', + } + ] + + self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) From 1cf78171c6ae001f324b22e0eb0e75e9fd587fe0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 11 Jul 2017 15:57:45 +0200 Subject: [PATCH 129/342] fixed expected output for station mode without encryption --- tests/airos/test_wpasupplicant.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 693089207..b8d105bd7 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -66,9 +66,8 @@ def test_no_encryption(self): expected = [ { 'device.1.profile': 'AUTO', - 'device.1.status': 'disabled', + 'device.1.status': 'enabled', 'profile.1.name': 'AUTO', - 'profile.1.network.1.phase2=auth': 'MSCHAPV2', 'profile.1.network.1.ssid': 'ap-ssid-example', 'profile.1.network.1.priority': 100, 'profile.1.network.1.key_mgmt.1.name': 'NONE', From ed0cb7a0ecf6d029aff7f5262445559433cb869e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 11 Jul 2017 16:56:47 +0200 Subject: [PATCH 130/342] fixed expected output for access point without encryption --- tests/airos/test_wpasupplicant.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index b8d105bd7..540eea660 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -228,15 +228,18 @@ def test_no_encryption(self): expected = [ { + 'status': 'enabled', + }, + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'profile.1.network.1.priority': 100, 'profile.1.network.1.ssid': 'ap-ssid-example', 'profile.1.network.1.key_mgmt.1.name': 'NONE', 'profile.1.network.2.key_mgmt.1.name': 'NONE', 'profile.1.network.2.priority': 2, 'profile.1.network.2.status': 'disabled', }, - { - 'status': 'enabled', - } ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) From 27acd5f6cdf0833176381f08aa48fe397e0d522c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 11 Jul 2017 16:58:06 +0200 Subject: [PATCH 131/342] [wpasupplicant] last test needed and last changes to make test pass --- netjsonconfig/backends/airos/converters.py | 19 +++++- netjsonconfig/backends/airos/wpasupplicant.py | 7 ++ tests/airos/test_wpasupplicant.py | 67 ++++++++++++++++--- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 80af94476..1e9c69c59 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -720,18 +720,34 @@ def _access_point_intermediate(self, original): result = [] ap_auth_protocols = available_mode_authentication['access_point'] + temp_dev = { + 'profile': 'AUTO', + 'status': 'disabled', + } + if original: head = original[0] if 'encryption' in head: network = ap_auth_protocols.get(head['encryption']['protocol'])(head) + result.append({ + 'status': 'disabled', + }) else: network = ap_auth_protocols['none'](head) + temp_dev['status'] = 'enabled' + result.append({ + 'status': 'enabled', + }) result.append({ + 'device': [ + temp_dev, + ], 'profile': [ { + 'name': 'AUTO', 'network': [ network, self.secondary_network(), @@ -739,9 +755,6 @@ def _access_point_intermediate(self, original): }, ], }) - result.append({ - 'status': 'enabled', - }) return (('wpasupplicant', result),) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index a8f83884c..72a25314f 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -5,6 +5,7 @@ def ap_no_encryption(interface): """ return { 'ssid': interface['wireless']['ssid'], + 'priority': 100, 'key_mgmt': [ { 'name': 'NONE', @@ -22,6 +23,12 @@ def ap_wpa2_personal(interface): return { 'psk': interface['encryption']['key'], 'ssid': interface['wireless']['ssid'], + 'key_mgmt': [ + { + 'name': 'NONE', + }, + ], + 'priority': 100, } diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 540eea660..502e5d636 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -54,9 +54,9 @@ def test_no_encryption(self): "ssid": "ap-ssid-example", "bssid": "00:11:22:33:44:55", }, - "encryption": { - "protocol": "none", - }, +# "encryption": { +# "protocol": "none", +# }, } ] }) @@ -130,7 +130,7 @@ def test_wpa2_personal(self): }, { 'status': 'enabled', - } + }, ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) @@ -163,6 +163,9 @@ def test_wpa2_enterprise(self): o.to_intermediate() expected = [ + { + 'status': 'enabled', + }, { 'device.1.profile': 'AUTO', 'device.1.status': 'enabled', @@ -185,9 +188,6 @@ def test_wpa2_enterprise(self): 'profile.1.network.2.priority': 2, 'profile.1.network.2.status': 'disabled', }, - { - 'status': 'enabled', - } ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) @@ -203,6 +203,50 @@ class TestWpasupplicantAccess(ConverterTest): def test_no_encryption(self): + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "autostart": True, + "wireless": { + "radio": "radio0", + "mode": "access_point", + "ssid": "ap-ssid-example", + }, +# "encryption": { +# "protocol": "none", +# }, + }, + ] + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'enabled', + }, + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + ] + + self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) + + def test_wpa2_personal(self): + o = self.backend({ "interfaces": [ { @@ -218,7 +262,8 @@ def test_no_encryption(self): "ssid": "ap-ssid-example", }, "encryption": { - "protocol": "none", + "protocol": "wpa2_personal", + "key": "cucumber", }, } ] @@ -228,13 +273,15 @@ def test_no_encryption(self): expected = [ { - 'status': 'enabled', + 'status': 'disabled', }, { 'device.1.profile': 'AUTO', - 'device.1.status': 'enabled', + 'device.1.status': 'disabled', + 'profile.1.name': 'AUTO', 'profile.1.network.1.priority': 100, 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.psk': 'cucumber', 'profile.1.network.1.key_mgmt.1.name': 'NONE', 'profile.1.network.2.key_mgmt.1.name': 'NONE', 'profile.1.network.2.priority': 2, From 7223833768ff5ba48ed8381b64ea5875704f68da Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 12 Jul 2017 11:43:52 +0200 Subject: [PATCH 132/342] [airos] always run converter from netjson to native configuration --- netjsonconfig/backends/airos/converters.py | 65 +++++++++++++--------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 1e9c69c59..8b487bbfd 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -13,7 +13,18 @@ def status(config, key='disabled'): return 'enabled' -class Aaa(BaseConverter): +class AirOSConverter(BaseConverter): + """ + Always run the converter from NetJSON + to native + """ + + @classmethod + def should_run_forward(cls, config): + return True + + +class Aaa(AirOSConverter): netjson_key = 'general' def wpa2_personal(self): @@ -70,7 +81,7 @@ def to_intermediate(self): return (('aaa', result),) -class Bridge(BaseConverter): +class Bridge(AirOSConverter): netjson_key = 'interfaces' def to_intermediate(self): @@ -107,7 +118,7 @@ def to_intermediate(self): return (('bridge', result),) -class Discovery(BaseConverter): +class Discovery(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -122,7 +133,7 @@ def to_intermediate(self): return (('discovery', result),) -class Dyndns(BaseConverter): +class Dyndns(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -139,7 +150,7 @@ def to_intermediate(self): return (('dyndns', result),) -class Ebtables(BaseConverter): +class Ebtables(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -158,7 +169,7 @@ def to_intermediate(self): return (('httpd', result),) -class Gui(BaseConverter): +class Gui(AirOSConverter): netjson_key = 'gui' def to_intermediate(self): @@ -177,7 +188,7 @@ def to_intermediate(self): return (('gui', result),) -class Httpd(BaseConverter): +class Httpd(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -200,7 +211,7 @@ def to_intermediate(self): return (('httpd', result),) -class Igmpproxy(BaseConverter): +class Igmpproxy(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -213,7 +224,7 @@ def to_intermediate(self): return (('igmpproxy', result),) -class Iptables(BaseConverter): +class Iptables(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -232,7 +243,7 @@ def to_intermediate(self): return (('iptables', result),) -class Netconf(BaseConverter): +class Netconf(AirOSConverter): netjson_key = 'interfaces' def type_to_role(self, typestr): @@ -307,7 +318,7 @@ def to_intermediate(self): return (('netconf', result),) -class Netmode(BaseConverter): +class Netmode(AirOSConverter): netjson_key = 'interfaces' def to_intermediate(self): @@ -319,7 +330,7 @@ def to_intermediate(self): return (('netmode', result), ) -class Ntpclient(BaseConverter): +class Ntpclient(AirOSConverter): netjson_key = 'ntp_servers' def to_intermediate(self): @@ -347,7 +358,7 @@ def to_intermediate(self): return (('ntpclient', result),) -class Pwdog(BaseConverter): +class Pwdog(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -389,7 +400,7 @@ def to_intermediate(self): return (('radio', result),) -class Resolv(BaseConverter): +class Resolv(AirOSConverter): netjson_key = 'dns_servers' def host(self): @@ -428,7 +439,7 @@ def to_intermediate(self): return (('resolv', result),) -class Route(BaseConverter): +class Route(AirOSConverter): netjson_key = 'routes' def to_intermediate(self): @@ -454,7 +465,7 @@ def to_intermediate(self): return (('route', result),) -class Snmp(BaseConverter): +class Snmp(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -470,7 +481,7 @@ def to_intermediate(self): return (('snmp', result),) -class Sshd(BaseConverter): +class Sshd(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -486,7 +497,7 @@ def to_intermediate(self): return (('sshd', result),) -class Syslog(BaseConverter): +class Syslog(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -498,7 +509,7 @@ def to_intermediate(self): return (('syslog', result),) -class System(BaseConverter): +class System(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -524,7 +535,7 @@ def to_intermediate(self): return (('system', result),) -class Telnetd(BaseConverter): +class Telnetd(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -537,7 +548,7 @@ def to_intermediate(self): return (('telnetd', result),) -class Tshaper(BaseConverter): +class Tshaper(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -545,7 +556,7 @@ def to_intermediate(self): return (('tshaper', [{'status': 'disabled', }]),) -class Unms(BaseConverter): +class Unms(AirOSConverter): netjson_keu = 'general' def to_intermediate(self): @@ -553,7 +564,7 @@ def to_intermediate(self): return (('unms', [{'status': 'disabled'}]),) -class Update(BaseConverter): +class Update(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -567,7 +578,7 @@ def to_intermediate(self): return (('update', result),) -class Users(BaseConverter): +class Users(AirOSConverter): netjson_key = 'general' def to_intermediate(self): @@ -588,7 +599,7 @@ def to_intermediate(self): return (('users', result),) -class Vlan(BaseConverter): +class Vlan(AirOSConverter): netjson_key = 'interfaces' def to_intermediate(self): @@ -614,7 +625,7 @@ def to_intermediate(self): return (('vlan', result),) -class Wireless(BaseConverter): +class Wireless(AirOSConverter): netjson_key = 'interfaces' def to_intermediate(self): @@ -664,7 +675,7 @@ def to_intermediate(self): return (('wireless', result),) -class Wpasupplicant(BaseConverter): +class Wpasupplicant(AirOSConverter): netjson_key = 'interfaces' def _station_intermediate(self, original): From 894e1702ae8f29006398d1c47ea0bc5bbba85fba Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 12 Jul 2017 11:44:42 +0200 Subject: [PATCH 133/342] [airos][aaa] update aaa converter default output --- netjsonconfig/backends/airos/converters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 8b487bbfd..ec7eb9aca 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -71,6 +71,7 @@ def to_intermediate(self): }, ], }, + 'status': 'disabled', } ]) From 67233017e2f178c2ed42ddfae6212713572c7113 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 12 Jul 2017 12:03:57 +0200 Subject: [PATCH 134/342] make syntastic happy --- netjsonconfig/backends/airos/converters.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index ec7eb9aca..b0f1e78de 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -6,6 +6,7 @@ from wpasupplicant import available_mode_authentication + def status(config, key='disabled'): if config.get(key): return 'disabled' @@ -46,7 +47,7 @@ def is_wpa2_personal(interface): return interface['encryption']['protocol'] == 'wpa2_personal' try: - return [ get_psk(i) for i in wireless if is_wpa2_personal(i)][0] + return [get_psk(i) for i in wireless if is_wpa2_personal(i)][0] except IndexError: return {} @@ -268,7 +269,7 @@ def to_intermediate(self): } # handle interface type quirks - if interface['type'] == 'ethernet' and not '.' in interface['name']: + if interface['type'] == 'ethernet' and '.' not in interface['name']: base['autoneg'] = 'enabled' base['flowcontrol'] = { 'rx': { @@ -424,7 +425,7 @@ def nameserver(self): 'status': 'enabled', }) - return { 'nameserver': t } + return {'nameserver': t} def to_intermediate(self): result = [] From 3309d6d53bf58079760091997448ad2662e20345 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 12 Jul 2017 12:04:14 +0200 Subject: [PATCH 135/342] KeyError handling when searching a ecnryption object from aaa converter --- netjsonconfig/backends/airos/converters.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index b0f1e78de..290053f55 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -44,7 +44,10 @@ def get_psk(interface): return t def is_wpa2_personal(interface): - return interface['encryption']['protocol'] == 'wpa2_personal' + try: + return interface['encryption']['protocol'] == 'wpa2_personal' + except: + return False try: return [get_psk(i) for i in wireless if is_wpa2_personal(i)][0] From f203c35d84ba9f5ce5e2b7fc9e140d4f4e9f4980 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 12 Jul 2017 12:49:02 +0200 Subject: [PATCH 136/342] [airos] add default values to every instance of get_copy --- netjsonconfig/backends/airos/converters.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 290053f55..5471ac447 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -33,7 +33,7 @@ def wpa2_personal(self): When using wpa_personal the wifi password is written in ``aaa.1.wpa.psk`` instead of ``wpasupplicant`` """ - wireless = [ i for i in get_copy(self.netjson, 'interfaces') if i['type'] == 'wireless'] + wireless = [i for i in get_copy(self.netjson, 'interfaces', []) if i['type'] == 'wireless'] def get_psk(interface): t = { @@ -93,7 +93,7 @@ def to_intermediate(self): result = [] original = [ - i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'bridge' + i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'bridge' ] bridges = [] @@ -256,12 +256,12 @@ def type_to_role(self, typestr): 'ethernet': 'mlan', 'bridge': 'mlan', } - return roles.get(typestr,'') + return roles.get(typestr, '') def to_intermediate(self): result = [] interfaces = [] - original = get_copy(self.netjson, self.netjson_key) + original = get_copy(self.netjson, self.netjson_key, []) for interface in original: base = { @@ -384,7 +384,7 @@ class Radio(BaseConverter): def to_intermediate(self): result = [] - original = get_copy(self.netjson, self.netjson_key) + original = get_copy(self.netjson, self.netjson_key, []) radios = [] @@ -418,7 +418,7 @@ def host(self): } def nameserver(self): - original = get_copy(self.netjson, self.netjson_key) + original = get_copy(self.netjson, self.netjson_key, []) t = [] @@ -449,7 +449,7 @@ class Route(AirOSConverter): def to_intermediate(self): result = [] - original = get_copy(self.netjson, self.netjson_key) + original = get_copy(self.netjson, self.netjson_key, []) routes = [] @@ -610,7 +610,7 @@ class Vlan(AirOSConverter): def to_intermediate(self): result = [] original = [ - i for i in get_copy(self.netjson, self.netjson_key) if '.' in i['name'] + i for i in get_copy(self.netjson, self.netjson_key, []) if '.' in i['name'] ] vlans = [] @@ -636,7 +636,7 @@ class Wireless(AirOSConverter): def to_intermediate(self): result = [] original = [ - i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'wireless' + i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'wireless' ] ws = [] @@ -790,7 +790,7 @@ def secondary_network(self): def to_intermediate(self): original = [ - i for i in get_copy(self.netjson, self.netjson_key) if i['type'] == 'wireless' + i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'wireless' ] if original: From bde97424a482a749993ffee233c5f546ffe8227e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 13 Jul 2017 12:32:15 +0200 Subject: [PATCH 137/342] fix syntastic warning --- netjsonconfig/backends/airos/airos.py | 2 +- netjsonconfig/backends/airos/converters.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 8e28c063f..8aaf25a09 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -51,7 +51,7 @@ class AirOS(BaseBackend): def to_intermediate(self): super(AirOS, self).to_intermediate() - for k,v in self.intermediate_data.items(): + for k, v in self.intermediate_data.items(): self.intermediate_data[k] = filter(lambda x: x != {}, flatten(intermediate_to_list(v))) def flatten(xs): diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 5471ac447..8def5c623 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -1,5 +1,5 @@ from copy import deepcopy -from ...utils import get_copy, sorted_dict +from ...utils import get_copy from ..base.converter import BaseConverter from ipaddress import ip_interface @@ -463,7 +463,6 @@ def to_intermediate(self): }) result.append(routes) - result.append({ 'status': 'enabled', }) From 2c015d3e38d0ffc563203393c9e4e68d336bd557 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 10:53:34 +0200 Subject: [PATCH 138/342] [airos] added missing converters to airos backend --- netjsonconfig/backends/airos/airos.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 8aaf25a09..010e79e92 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -22,8 +22,11 @@ class AirOS(BaseBackend): Bridge, Discovery, Dyndns, + Ebtables, Gui, Httpd, + Igmpproxy, + Iptables, Netconf, Netmode, Ntpclient, From a35f41dd7a812a69ad167bfbcddd02d82a4241c8 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 10:54:56 +0200 Subject: [PATCH 139/342] [airos] fixed wrong ebtables converter output namespace --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 8def5c623..04b56e3de 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -171,7 +171,7 @@ def to_intermediate(self): }, ] - return (('httpd', result),) + return (('ebtables', result),) class Gui(AirOSConverter): From 09df88b7f2bb9a1c80658fbf1c24d81585ee6198 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 10:56:11 +0200 Subject: [PATCH 140/342] [airos] fixed default values for converters --- netjsonconfig/backends/airos/converters.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 04b56e3de..19b37d9f7 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -163,7 +163,7 @@ def to_intermediate(self): { 'sys': { 'fw': { - 'status': 'enabled', + 'status': 'disabled', }, 'status': 'enabled', }, @@ -222,7 +222,7 @@ class Igmpproxy(AirOSConverter): def to_intermediate(self): result = [ { - 'status': 'enabled', + 'status': 'disabled', }, ] @@ -237,11 +237,11 @@ def to_intermediate(self): { 'sys': { 'portfw': { - 'status': 'enabled', + 'status': 'disabled', }, 'status': 'enabled', }, - 'status': 'enabled', + 'status': 'disabled', }, ] @@ -508,7 +508,11 @@ def to_intermediate(self): result = [] result.append({ - 'status': 'disabled', + 'remote': { + 'port': 514, + 'status': 'disabled', + }, + 'status': 'enabled', }) return (('syslog', result),) From 0229fdd174ccecde288344b0892379c551409c97 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 11:02:48 +0200 Subject: [PATCH 141/342] [airos] fixed route converter to use values from netjson --- netjsonconfig/backends/airos/converters.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 19b37d9f7..5d3d4d411 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -454,11 +454,14 @@ def to_intermediate(self): routes = [] for r in original: + network = ip_interface(r['destination']) + temp = {} + temp['ip'] = str(network.ip) + temp['netmask'] = str(network.netmask) routes.append({ - 'devname': '', - 'gateway': '0.0.0.0', - 'ip': '0.0.0.0', - 'netmask': 0, + 'gateway': r['next'], + 'ip': temp['ip'], + 'netmask': temp['netmask'], 'status': 'enabled', }) From 6d7a24c8ef85cb3656d40f4b90c54fe7f096d034 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 11:04:30 +0200 Subject: [PATCH 142/342] [airos] removed unused templates from project --- .../backends/airos/templates/network.jinja2 | 23 ------------------- .../backends/airos/templates/system.jinja2 | 15 ------------ 2 files changed, 38 deletions(-) delete mode 100644 netjsonconfig/backends/airos/templates/network.jinja2 delete mode 100644 netjsonconfig/backends/airos/templates/system.jinja2 diff --git a/netjsonconfig/backends/airos/templates/network.jinja2 b/netjsonconfig/backends/airos/templates/network.jinja2 deleted file mode 100644 index 34cfd3469..000000000 --- a/netjsonconfig/backends/airos/templates/network.jinja2 +++ /dev/null @@ -1,23 +0,0 @@ -{% if not is_empty %} - - {% for i, entry in bridge %} - bridge.{{ i + 1}}.comment= - bridge.{{ i + 1}}.devname={{ entry.name }} - bridge.{{ i + 1}}.port.1.devname={{ entry.bridge_members[0] }} - bridge.{{ i + 1}}.port.1.status=enabled - bridge.{{ i + 1}}.port.2.devname={{ entry.bridge_members[1] }} - bridge.{{ i + 1}}.port.2.status=enabled - bridge.{{ i + 1}}.status=enabled - bridge.{{ i + 1}}.stp.status={{ entry.stp | default("disabled") }} - {% endfor %} - - bridge.status=enabled - - {% for i, entry in vlan %} - vlan.{{ i + 1}}.comment={{ entry.comment | default("") }} - vlan.{{ i + 1}}.devname={{ entry.devname }} - vlan.{{ i + 1}}.id={{ entry.id }} - vlan.{{ i + 1}}.status={{ entry.status }} - {% endfor %} - -{% endif %} diff --git a/netjsonconfig/backends/airos/templates/system.jinja2 b/netjsonconfig/backends/airos/templates/system.jinja2 deleted file mode 100644 index 634a5e174..000000000 --- a/netjsonconfig/backends/airos/templates/system.jinja2 +++ /dev/null @@ -1,15 +0,0 @@ -{% if not is_empty %} -resolv.status=disable -resolv.nameserver.status=enabled -{% for i, entry in resolv.nameserver %} -resolv.nameserver.{{ i + 1 }}.status=enabled -resolv.nameserver.{{ i + 1}}.ip={{ entry }} -{% endfor %} - -system.timezone={{ system.timezone }} -system.longitude={{ system.longitude }} -system.latitude={{ system.latitude }} -system.date.timestamp={{ system.timestamp }} -system.button.reset={{ system.reset }} - -{% endif %} From 9047b02d822c0f6c129379d8c78b5acd31156a07 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 29 Jun 2017 15:47:50 +0200 Subject: [PATCH 143/342] hardcode netmode value in template --- netjsonconfig/backends/airos/templates/airos.jinja2 | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/netjsonconfig/backends/airos/templates/airos.jinja2 b/netjsonconfig/backends/airos/templates/airos.jinja2 index 0ae7deccb..1827d4e69 100644 --- a/netjsonconfig/backends/airos/templates/airos.jinja2 +++ b/netjsonconfig/backends/airos/templates/airos.jinja2 @@ -1,7 +1,11 @@ {% for namespace, block in data.items() %} - {% for element in block %} - {% for k,v in element.items() %} - {{ namespace }}.{{ k }}={{ v }} + {% if namespace != 'netmode' %} + {% for element in block %} + {% for k,v in element.items() %} + {{ namespace }}.{{ k }}={{ v }} + {% endfor %} {% endfor %} - {% endfor %} + {% else %} + netmode=bridge + {% endif %} {% endfor %} From ac48bb4a9aa143867b971e8685e96e9b5f2e7a4c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 11:19:12 +0200 Subject: [PATCH 144/342] fix syntastic warning in airos template --- netjsonconfig/backends/airos/templates/airos.jinja2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/templates/airos.jinja2 b/netjsonconfig/backends/airos/templates/airos.jinja2 index 1827d4e69..0dcc1a60e 100644 --- a/netjsonconfig/backends/airos/templates/airos.jinja2 +++ b/netjsonconfig/backends/airos/templates/airos.jinja2 @@ -1,7 +1,7 @@ {% for namespace, block in data.items() %} {% if namespace != 'netmode' %} {% for element in block %} - {% for k,v in element.items() %} + {% for k, v in element.items() %} {{ namespace }}.{{ k }}={{ v }} {% endfor %} {% endfor %} From 489901852aa3cccdfe07f2efaea2bed9d8c3ce3b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 11:19:39 +0200 Subject: [PATCH 145/342] [airos][template] fixed netmode template namespace the only section differing in the airos file is the netmode value and it requires a different template formatter, moreover as it is a single value we can hardcode the key that we require to render --- netjsonconfig/backends/airos/templates/airos.jinja2 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/templates/airos.jinja2 b/netjsonconfig/backends/airos/templates/airos.jinja2 index 0dcc1a60e..4f959a034 100644 --- a/netjsonconfig/backends/airos/templates/airos.jinja2 +++ b/netjsonconfig/backends/airos/templates/airos.jinja2 @@ -6,6 +6,8 @@ {% endfor %} {% endfor %} {% else %} - netmode=bridge + {% for element in block %} + netmode={{ element['status'] }} + {% endfor %} {% endif %} {% endfor %} From 20105e32f9d47c149b64b5231aa5c622ba9ebc01 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 11:28:48 +0200 Subject: [PATCH 146/342] [airos][schema] add netmode property to airos schema --- netjsonconfig/backends/airos/schema.py | 28 +++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 46ea06459..7196adee8 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -30,6 +30,27 @@ } } +""" +This schema defines a new property for netjson + +As the antenna can be in ``bridge`` or ``router`` mode +this mode can be selected from this property +""" + +netmode_schema = { + "type": "object", + "properties": { + "netmode": { + "enum": [ + "bridge", + "router", + ], + "default": "bridge", + "type": "string", + }, + }, + } + """ This schema override the possible encryption for AirOS from the default schema """ @@ -67,4 +88,9 @@ } -schema = merge_config(default_schema, netconf_schema, wpasupplicant_schema) +schema = merge_config( + default_schema, + netconf_schema, + netmode_schema, + wpasupplicant_schema, + ) From bfb50ca79940fb86438626f4e9cf5ba86f511590 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 12:59:12 +0200 Subject: [PATCH 147/342] [airos] set netmode from input netjson in converter --- netjsonconfig/backends/airos/converters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 5d3d4d411..714423c68 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -324,13 +324,13 @@ def to_intermediate(self): class Netmode(AirOSConverter): - netjson_key = 'interfaces' + netjson_key = 'netmode' def to_intermediate(self): result = [] result.append({ - 'status': 'enabled', + 'status': self.netjson.get('netmode', 'bridge'), }) return (('netmode', result), ) From 8bb498c4ea041a60f64c09aea127f0e70695fcb2 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 13:04:17 +0200 Subject: [PATCH 148/342] [test] added test for netmode sectio#n --- tests/airos/dummy.py | 2 +- tests/airos/test_netmode.py | 52 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/airos/test_netmode.py diff --git a/tests/airos/dummy.py b/tests/airos/dummy.py index 7b0e90477..949228269 100644 --- a/tests/airos/dummy.py +++ b/tests/airos/dummy.py @@ -93,7 +93,7 @@ class NetconfAirOS(AirOS): ] -class Netmode(AirOS): +class NetmodeAirOS(AirOS): """ Mock backend with converter for network mode """ diff --git a/tests/airos/test_netmode.py b/tests/airos/test_netmode.py new file mode 100644 index 000000000..b3857f98d --- /dev/null +++ b/tests/airos/test_netmode.py @@ -0,0 +1,52 @@ +from unittest import skip +from .dummy import NetmodeAirOS, ConverterTest + + +class TestNetmodeConverter(ConverterTest): + + backend = NetmodeAirOS + + def test_netconf_key(self): + o = self.backend({ + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'bridge', + }, + ] + + self.assertEqualConfig(o.intermediate_data['netmode'], expected) + + def test_bridge(self): + o = self.backend({ + 'netmode': 'bridge', + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'bridge', + }, + ] + + self.assertEqualConfig(o.intermediate_data['netmode'], expected) + + def test_router(self): + o = self.backend({ + 'netmode': 'router', + }) + + o.to_intermediate() + + expected = [ + { + 'status': 'router', + }, + ] + + + self.assertEqualConfig(o.intermediate_data['netmode'], expected) From e6f5dfb9e38ca8c2c32aab0601cbf56479dff06c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 15:25:36 +0200 Subject: [PATCH 149/342] [airos]retab template --- .../backends/airos/templates/airos.jinja2 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/netjsonconfig/backends/airos/templates/airos.jinja2 b/netjsonconfig/backends/airos/templates/airos.jinja2 index 4f959a034..c92fc55ff 100644 --- a/netjsonconfig/backends/airos/templates/airos.jinja2 +++ b/netjsonconfig/backends/airos/templates/airos.jinja2 @@ -1,13 +1,13 @@ {% for namespace, block in data.items() %} - {% if namespace != 'netmode' %} - {% for element in block %} - {% for k, v in element.items() %} - {{ namespace }}.{{ k }}={{ v }} - {% endfor %} - {% endfor %} - {% else %} + {% if namespace != 'netmode' %} + {% for element in block %} + {% for k, v in element.items() %} + {{ namespace }}.{{ k }}={{ v }} + {% endfor %} + {% endfor %} + {% else %} {% for element in block %} netmode={{ element['status'] }} {% endfor %} - {% endif %} + {% endif %} {% endfor %} From 149c124b5f36ea093ec66635c87db8dbbf678e31 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 14 Jul 2017 15:41:17 +0200 Subject: [PATCH 150/342] [airos] fixed relative import in converters --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 714423c68..7253c6d6a 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,7 +4,7 @@ from ipaddress import ip_interface -from wpasupplicant import available_mode_authentication +from .wpasupplicant import available_mode_authentication def status(config, key='disabled'): From 05ceead6243755954ebc1db0cc9b6a8ee3dfdec6 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sat, 15 Jul 2017 12:25:11 +0200 Subject: [PATCH 151/342] iterate with list comprehension instead of filte --- netjsonconfig/backends/airos/airos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 010e79e92..0160c1551 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -55,7 +55,7 @@ class AirOS(BaseBackend): def to_intermediate(self): super(AirOS, self).to_intermediate() for k, v in self.intermediate_data.items(): - self.intermediate_data[k] = filter(lambda x: x != {}, flatten(intermediate_to_list(v))) + self.intermediate_data[k] = [ x for x in flatten(intermediate_to_list(v)) if x != {}] def flatten(xs): """ From 669f1a1e44297a6294c8015a95df504b079708d9 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sat, 15 Jul 2017 12:26:08 +0200 Subject: [PATCH 152/342] replace values at the end of the loop instead of updating inside --- netjsonconfig/backends/airos/airos.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 0160c1551..882aaa52d 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -126,18 +126,20 @@ def intermediate_to_list(configuration): elif isinstance(element, tuple): (index, config) = element # update the keys to prefix the index + temp = {} for k, v in config.items(): # write the new key - config['{i}.{key}'.format(i=index + 1, key=k)] = v - # delete the old - del config[k] + temp['{i}.{key}'.format(i=index + 1, key=k)] = v + + config = temp # now the keys are updated with the index # reduce to atoms the new config # by recursively calling yourself # on a list containing the new atom result = result + intermediate_to_list([config]) - elif isinstance(element, dict): + elif isinstance(element, dict): + temp = {} for k, v in element.items(): if isinstance(v, string_types) or isinstance(v, int): pass @@ -149,13 +151,10 @@ def intermediate_to_list(configuration): for sk, sv in son.items(): nested_key = '{key}.{subkey}'.format(key=k, subkey=sk) - element[nested_key] = sv - - # remove the nested object - del element[k] + temp[nested_key] = sv # now it is atomic, append it to - result.append(element) + result.append(temp) else: raise Exception('malformed intermediate representation') From a58653dcea3455228d1d18ed7b7892c6f94c03f3 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sat, 15 Jul 2017 12:26:50 +0200 Subject: [PATCH 153/342] [airos] added type hints for recursive function --- netjsonconfig/backends/airos/airos.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 882aaa52d..1ae2325ff 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -73,8 +73,8 @@ def intermediate_to_list(configuration): - list -> prepend the list index to every item key - dictionary -> prepend the father key to every key - configuration :: list - return list + configuration :: List[Enum[Dict,List]] + return List[Dict] >>> intermediate_to_list([ { From 0504720ed085926cdcb06a9630be4e22b01d9679 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 16 Jul 2017 13:40:55 +0200 Subject: [PATCH 154/342] [airos][doc] added netmode docs for airos backend --- docs/source/backends/airos.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 7d736e279..e342ef5c2 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -111,6 +111,17 @@ The default values for this key are as reported below } } +Netmode +------- + +AirOS v8.3 can operate in ``bridge`` and ``router`` mode (but defaults to ``bridge``) and this can be specified with the ``netmode`` property + +.. code-block:: json + + { + "netmode": "bridge" + } + NTP servers ----------- From 68b3fde611014054d647dbf9c1a92383ba0f8056 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Sun, 16 Jul 2017 15:26:30 +0200 Subject: [PATCH 155/342] make flake happier --- netjsonconfig/backends/airos/airos.py | 16 +++++++---- netjsonconfig/backends/airos/converters.py | 3 ++ netjsonconfig/backends/airos/schema.py | 17 ++++++++++-- tests/airos/dummy.py | 32 +++++++++++++++++++++- tests/airos/test_bridge.py | 2 -- tests/airos/test_discovery.py | 1 - tests/airos/test_netconf.py | 5 ++-- tests/airos/test_netmode.py | 2 -- 8 files changed, 60 insertions(+), 18 deletions(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 1ae2325ff..391b41f28 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -1,12 +1,14 @@ -import re - from six import string_types -from .converters import * +from .converters import Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, \ + Httpd, Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, \ + Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, System, \ + Telnetd, Update, Users, Vlan, Wireless, Wpasupplicant from .renderers import AirOS from ..base.backend import BaseBackend from .schema import schema + class AirOS(BaseBackend): """ AirOS backend @@ -55,7 +57,8 @@ class AirOS(BaseBackend): def to_intermediate(self): super(AirOS, self).to_intermediate() for k, v in self.intermediate_data.items(): - self.intermediate_data[k] = [ x for x in flatten(intermediate_to_list(v)) if x != {}] + self.intermediate_data[k] = [x for x in flatten(intermediate_to_list(v)) if x != {}] + def flatten(xs): """ @@ -64,7 +67,8 @@ def flatten(xs): if xs is not list: return xs else: - return reduce(lambda x,y: x + flatten(y), xs, []) + return reduce(lambda x, y: x + flatten(y), xs, []) + def intermediate_to_list(configuration): """ @@ -153,7 +157,7 @@ def intermediate_to_list(configuration): nested_key = '{key}.{subkey}'.format(key=k, subkey=sk) temp[nested_key] = sv - # now it is atomic, append it to + # now it is atomic, append it to result.append(temp) else: diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 7253c6d6a..61f91da01 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -804,3 +804,6 @@ def to_intermediate(self): # call either ``_station_intermediate`` or ``_access_point_intermediate`` # and return the result return getattr(self, '_%s_intermediate' % head['wireless']['mode'])(original) + + +__all__ = [] diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 7196adee8..5c0a2806f 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -5,6 +5,14 @@ from ...schema import DEFAULT_FILE_MODE # noqa - backward compatibility from ...utils import merge_config + +def merge_list(schema_list): + schema = default_schema + for s in schema_list: + schema = merge_config(schema, s) + return schema + + """ This defines a new property in the ``Interface``. @@ -30,13 +38,13 @@ } } + """ This schema defines a new property for netjson As the antenna can be in ``bridge`` or ``router`` mode this mode can be selected from this property """ - netmode_schema = { "type": "object", "properties": { @@ -51,6 +59,7 @@ }, } + """ This schema override the possible encryption for AirOS from the default schema """ @@ -88,9 +97,11 @@ } -schema = merge_config( +schema = merge_list([ default_schema, netconf_schema, netmode_schema, wpasupplicant_schema, - ) + ]) + +__all__ = [schema] diff --git a/tests/airos/dummy.py b/tests/airos/dummy.py index 949228269..e539982a6 100644 --- a/tests/airos/dummy.py +++ b/tests/airos/dummy.py @@ -1,6 +1,9 @@ from netjsonconfig import AirOS -from netjsonconfig.backends.airos.converters import * +from netjsoncongig.backends.airos.converters import Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, \ + Httpd, Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, \ + Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, System, \ + Telnetd, Update, Users, Vlan, Wireless, Wpasupplicant from unittest import TestCase @@ -66,6 +69,15 @@ class DyndnsAirOS(AirOS): ] +class EbtablesAirOS(AirOS): + """ + Mock backend with converter for ebtables + """ + converters = [ + Ebtables, + ] + + class GuiAirOS(AirOS): """ Mock backend with converter for web interface settings @@ -84,6 +96,24 @@ class HttpdAirOS(AirOS): ] +class Igmpproxy(AirOS): + """ + Mock backend with converter for igmpproxy + """ + converters = [ + Igmpproxy, + ] + + +class IptablesAirOS(AirOS): + """ + Mock backend with converter for iptables + """ + converters = [ + Iptables, + ] + + class NetconfAirOS(AirOS): """ Mock backend with converter for network configuration diff --git a/tests/airos/test_bridge.py b/tests/airos/test_bridge.py index 61b6c9cc1..2ca0c717d 100644 --- a/tests/airos/test_bridge.py +++ b/tests/airos/test_bridge.py @@ -1,5 +1,3 @@ -from netjsonconfig.backends.airos.converters import * - from .dummy import BridgeAirOS, ConverterTest diff --git a/tests/airos/test_discovery.py b/tests/airos/test_discovery.py index b759ff75b..48812f03b 100644 --- a/tests/airos/test_discovery.py +++ b/tests/airos/test_discovery.py @@ -19,5 +19,4 @@ def test_discovery_key(self): }, ] - self.assertEqualConfig(o.intermediate_data['discovery'], expected) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index 1dc0cbbd6..c884fbc15 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -78,7 +78,6 @@ def test_inactive_interface(self): }, ] - self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_vlan(self): @@ -143,7 +142,7 @@ def test_management_vlan(self): 'status': 'enabled', }, ] - + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_access_point(self): @@ -445,7 +444,7 @@ def test_other(self): 'status': 'enabled', }, ] - + self.assertEqualConfig(o.intermediate_data['netconf'], expected) def test_more_interfaces(self): diff --git a/tests/airos/test_netmode.py b/tests/airos/test_netmode.py index b3857f98d..c7579456e 100644 --- a/tests/airos/test_netmode.py +++ b/tests/airos/test_netmode.py @@ -1,4 +1,3 @@ -from unittest import skip from .dummy import NetmodeAirOS, ConverterTest @@ -48,5 +47,4 @@ def test_router(self): }, ] - self.assertEqualConfig(o.intermediate_data['netmode'], expected) From 30e27f0ca85bb0f962e50d251581d0c3befbc766 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 17 Jul 2017 15:52:34 +0200 Subject: [PATCH 156/342] [airos] merged multiple schema overrides into one --- netjsonconfig/backends/airos/schema.py | 100 +++++++++---------------- 1 file changed, 36 insertions(+), 64 deletions(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 5c0a2806f..13ebccf40 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -6,13 +6,6 @@ from ...utils import merge_config -def merge_list(schema_list): - schema = default_schema - for s in schema_list: - schema = merge_config(schema, s) - return schema - - """ This defines a new property in the ``Interface``. @@ -23,7 +16,7 @@ def merge_list(schema_list): on a bridge """ -netconf_schema = { +override_schema = { "type": "object", "addtionalProperties": True, "definitions": { @@ -34,19 +27,38 @@ def merge_list(schema_list): "default": False, } } - } - } - } - - -""" -This schema defines a new property for netjson - -As the antenna can be in ``bridge`` or ``router`` mode -this mode can be selected from this property -""" -netmode_schema = { - "type": "object", + }, + "encryption_wireless_property_ap": { + "properties": { + "encryption": { + "type": "object", + "title": "Encryption", + "required": "protocol", + "propertyOrder": 20, + "oneOf": [ + {"$ref": "#/definitions/encryption_none"}, + {"$ref": "#/definitions/encryption_wpa_personal"}, + {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, + ], + }, + }, + }, + "encryption_wireless_property_sta": { + "properties": { + "encryption": { + "type": "object", + "title": "Encryption", + "required": "protocol", + "propertyOrder": 20, + "oneOf": [ + {"$ref": "#/definitions/encryption_none"}, + {"$ref": "#/definitions/encryption_wpa_personal"}, + {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, + ], + }, + }, + }, + }, "properties": { "netmode": { "enum": [ @@ -59,49 +71,9 @@ def merge_list(schema_list): }, } - -""" -This schema override the possible encryption for AirOS from the default schema -""" -wpasupplicant_schema = { - "encryption_wireless_property_sta": { - "properties": { - "encryption": { - "type": "object", - "title": "Encryption", - "required": "protocol", - "propertyOrder": 20, - "oneOf": [ - {"$ref": "#/definitions/encryption_none"}, - {"$ref": "#/definitions/encryption_wpa_personal"}, - {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, - ], - }, - }, - }, - "encryption_wireless_property_ap": { - "properties": { - "encryption": { - "type": "object", - "title": "Encryption", - "required": "protocol", - "propertyOrder": 20, - "oneOf": [ - {"$ref": "#/definitions/encryption_none"}, - {"$ref": "#/definitions/encryption_wpa_personal"}, - {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, - ], - }, - }, - }, -} - - -schema = merge_list([ +schema = merge_config( default_schema, - netconf_schema, - netmode_schema, - wpasupplicant_schema, - ]) + override_schema + ) __all__ = [schema] From ebbe92aeaa7f4bac9f1d3bb122171faddf29eb2b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 17 Jul 2017 16:22:30 +0200 Subject: [PATCH 157/342] [airos] complied with style issue from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [airos][test] be professional and wear a 👔 [airos][doc] changed to comply to pull request review [airos] renamed to more declarative name [airos] switched to friendlier names in dictionary iteration [airos] fixed invalid schema [airos] fixed empty dictionary result [airos] draft for key derivation in user converter [airos] draft for specifying user password from netjson [airos] included password as blob [airos] added doc about user password for airos [airos] fixed bugs with child_value being used as child_key [airos] added test for conversion from intermediate_data [airos] comply with PEP8 naming convention [airos] fixed indentation issues for dictionaries [airos] fixed typo in class attribute [airos] made indented code more compact [airos] fixed typo in converter class [airos] fixed indentation in list comprehension [airos] renamed to more readable name [airos] remove commented code [airos] made more compat code [airos] retabbed test code to use 4 spaces [airos] removed unused import [airos] changed to 4 spaces indentation in template [qa] Fixed flake8 & isort warnings [airos] renamed renderer for PEP8 compliance --- bin/netjsonconfig | 2 +- docs/source/backends/airos.rst | 32 +- docs/source/backends/intermediate.rst | 106 ++--- netjsonconfig/__init__.py | 2 +- netjsonconfig/backends/airos/airos.py | 74 ++- netjsonconfig/backends/airos/converters.py | 428 ++++++------------ netjsonconfig/backends/airos/renderers.py | 2 +- netjsonconfig/backends/airos/schema.py | 26 +- .../backends/airos/templates/airos.jinja2 | 22 +- netjsonconfig/backends/airos/wpasupplicant.py | 1 - tests/airos/{dummy.py => mock.py} | 124 ++--- tests/airos/test_aaa.py | 6 +- tests/airos/test_bridge.py | 124 +++-- tests/airos/test_discovery.py | 14 +- tests/airos/test_dyndns.py | 12 +- tests/airos/test_gui.py | 18 +- tests/airos/test_httpd.py | 24 +- tests/airos/test_intermediate.py | 20 + tests/airos/test_netconf.py | 391 ++++++++-------- tests/airos/test_netmode.py | 28 +- tests/airos/test_ntp.py | 60 ++- tests/airos/test_pwdog.py | 18 +- tests/airos/test_radio.py | 52 +-- tests/airos/test_resolv.py | 44 +- tests/airos/test_vlan.py | 160 +++---- tests/airos/test_wireless.py | 104 ++--- tests/airos/test_wpasupplicant.py | 221 ++++----- 27 files changed, 962 insertions(+), 1153 deletions(-) rename tests/airos/{dummy.py => mock.py} (63%) create mode 100644 tests/airos/test_intermediate.py diff --git a/bin/netjsonconfig b/bin/netjsonconfig index d62efbe07..2062eed72 100644 --- a/bin/netjsonconfig +++ b/bin/netjsonconfig @@ -170,7 +170,7 @@ backends = { 'openwrt': netjsonconfig.OpenWrt, 'openwisp': netjsonconfig.OpenWisp, 'openvpn': netjsonconfig.OpenVpn, - 'airos': netjsonconfig.AirOS, + 'airos': netjsonconfig.AirOs, } backend_class = backends[args.backend] diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index e342ef5c2..7b262cc0c 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -139,6 +139,36 @@ By setting the key ``ntp_servers`` in your input you can provide a list of ntp s ] } +Users +----- + +We can specify the user password as a blob divided into ``salt`` and ``hash``. + +From the antenna configuration take the user section. + +.. code-block:: ini + + users.status=enabled + users.1.status=enabled + users.1.name=ubnt + users.1.password=$1$yRo1tmtC$EcdoRX.JnD4VaEYgghgWg1 + +I the line ``users.1.password=$1$yRo1tmtC$EcdoRX.JnD4VaEYgghgWg1`` there are both the salt and the password hash in the format ``$ algorithm $ salt $ hash $``, e.g in the previous block ``algorithm=1``, ``salt=yRo1tmtC`` and ``hash=EcdoRX.JnD4VaEYgghgWg1``. + +To specify the password in NetJSON use the ``user`` property. + +.. code-block:: json + + { + "type": "DeviceConfiguration", + "user": { + "name": "ubnt", + "passsword": "EcdoRX.JnD4VaEYgghgWg1", + "salt": "yRo1tmtC" + } + } + + WPA2 ---- @@ -180,4 +210,4 @@ And another that set the authentication protocol to WPA2 enterprise, but this is ] } -Leaving the `NetJSON Encryption object ` empty defaults to no encryption at all +Leaving the `NetJSON Encryption object ` empty defaults to no encryption at all. diff --git a/docs/source/backends/intermediate.rst b/docs/source/backends/intermediate.rst index 507c80ec6..142034826 100644 --- a/docs/source/backends/intermediate.rst +++ b/docs/source/backends/intermediate.rst @@ -10,12 +10,12 @@ Intermediate representation The intermediate representation is the output of the a :ref:`converter`, it is backend specific and is built as a tree structure made from python -builtins values +builtins values. A tree is a *acyclic, directional graph* with an element called *root*. The root of our tree is stored in the first element of a tuple, along with -the root's direct sons as a list +the root's direct sons as a list: .. code-block:: python @@ -30,9 +30,9 @@ As an example here we present the tree `('spam', ['eggs', 'snakes'])` } As a son may be a carrier of a value so we store it in a dictionary instead of adding a *leaf* -with another level of recursion +with another level of recursion. -As an example here we present the tree `('spam', [ { 'eggs': 2 }, { 'snakes' : { 'loved' : 'python' }}])` +As an example here we present the tree `('spam', [ { 'eggs': 2 }, { 'snakes' : { 'loved' : 'python' }}])`: .. graphviz:: @@ -46,7 +46,7 @@ As an example here we present the tree `('spam', [ { 'eggs': 2 }, { 'snakes' : { } -This tree could be tranlated to a configuration file for AirOS that looks like this +This tree could be tranlated to a configuration file for AirOS that looks like this: .. code-block:: ini @@ -55,7 +55,7 @@ This tree could be tranlated to a configuration file for AirOS that looks like t So our tree representation is based on the simple assumption that a *leaf* is a dictionary -without nested values and nested values in a dictionary creates a father-son relationship +without nested values and nested values in a dictionary creates a father-son relationship. Instead when the configuration requires that the son values must be prefixed from a number, e.g. `vlan.1.devname=eth0` we store a list of dictionaries. @@ -65,27 +65,27 @@ e.g. `vlan.1.devname=eth0` we store a list of dictionaries. ( 'spam', [ - { - 'eggs' : 2, + { + 'eggs' : 2, + }, + { + 'snakes' : { + 'loved' : [ + { + 'python2' : True, + }, + { + 'python3' : True, + }, + { + 'ipython' : True, + } + ], }, - { - 'snakes' : { - 'loved' : [ - { - 'python2' : True, - }, - { - 'python3' : True, - }, - { - 'ipython' : True, - } - ], - }, - } - ] + } + ]) -And the resulting tree is this +And the resulting tree is: .. graphviz:: @@ -109,7 +109,7 @@ And the resulting tree is this } -And the configuration is +And the configuration is: .. code-block:: ini @@ -119,15 +119,15 @@ And the configuration is spam.snakes.loved.2.ipython=true The process by which we can go from the intermediate representation from -the output configuration is called flattening +the output configuration is called flattening, you can find more in the next section. Flattening ---------- To avoid at all cost a recursive logic in the template we flatten the intermediate -representation to something that has a *namespace* a *key* and a *value* +representation to something that has a *namespace* a *key* and a *value*. -This input NetJSON will be converted to a python :ref:`configuration_dictionary` +This input NetJSON will be converted to a python :ref:`configuration_dictionary`: .. code-block:: json @@ -135,16 +135,16 @@ This input NetJSON will be converted to a python :ref:`configuration_dictionary` { "type" : "DeviceConfiguration", "interfaces" : [ - { - "name" : "eth0.1", - "type" : "ethernet", - "comment" : "management vlan" - }, - { - "name" : "eth0.2", - "type" : "ethernet", - "comment" : "traffic vlan" - } + { + "name" : "eth0.1", + "type" : "ethernet", + "comment" : "management vlan" + }, + { + "name" : "eth0.2", + "type" : "ethernet", + "comment" : "traffic vlan" + } ] } @@ -153,21 +153,21 @@ This input NetJSON will be converted to a python :ref:`configuration_dictionary` #python { 'interfaces' : [ - { - 'name' : 'eth0.1', - 'type' : 'ethernet', - 'comment' : 'management' - }, - { - 'name' : 'eth0.2', - 'type' : 'ethernet', - 'comment' : 'traffic' - } + { + 'name' : 'eth0.1', + 'type' : 'ethernet', + 'comment' : 'management vlan' + }, + { + 'name' : 'eth0.2', + 'type' : 'ethernet', + 'comment' : 'traffic vlan' + } ] } -And this must be converted to an appropiate AirOS configuration which looks like this +And this must be converted to an appropiate AirOS configuration which looks like this: .. code-block:: ini @@ -182,7 +182,7 @@ And this must be converted to an appropiate AirOS configuration which looks like vlan.status=enabled To do this we must convert the :ref:`configuration_dictionary` into something that -resemble the target text, the output configuration +resembles the target text, the output configuration. .. code-block:: python @@ -208,9 +208,9 @@ resemble the target text, the output configuration ) -And to do that we get rid of the multiple indentation levels by flattening the tree structure +And to do that we get rid of the multiple indentation levels by flattening the tree structure. -The tree associated with the previous NetJSON example is this +The tree associated with the previous NetJSON example is this: .. graphviz:: diff --git a/netjsonconfig/__init__.py b/netjsonconfig/__init__.py index c068fdda6..d116bfe73 100644 --- a/netjsonconfig/__init__.py +++ b/netjsonconfig/__init__.py @@ -3,4 +3,4 @@ from .backends.openwrt.openwrt import OpenWrt # noqa from .backends.openwisp.openwisp import OpenWisp # noqa from .backends.openvpn.openvpn import OpenVpn # noqa -from .backends.airos.airos import AirOS # noqa +from .backends.airos.airos import AirOs # noqa diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 391b41f28..36236dcde 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -1,22 +1,22 @@ from six import string_types +from six.moves import reduce -from .converters import Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, \ - Httpd, Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, \ - Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, System, \ - Telnetd, Update, Users, Vlan, Wireless, Wpasupplicant -from .renderers import AirOS from ..base.backend import BaseBackend +from .converters import (Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, Httpd, + Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, + Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, + System, Telnetd, Update, Users, Vlan, Wireless, + Wpasupplicant) +from .renderers import AirOsRenderer from .schema import schema -class AirOS(BaseBackend): +class AirOs(BaseBackend): """ AirOS backend """ - # backend schema validator schema = schema - # converters from configuration # dictionary to intermediate representation converters = [ @@ -45,29 +45,27 @@ class AirOS(BaseBackend): Users, Vlan, Wireless, - Wpasupplicant + Wpasupplicant, ] - # the environment where airos # templates lives env_path = 'netjsonconfig.backends.airos' - - renderer = AirOS + renderer = AirOsRenderer def to_intermediate(self): - super(AirOS, self).to_intermediate() + super(AirOs, self).to_intermediate() for k, v in self.intermediate_data.items(): self.intermediate_data[k] = [x for x in flatten(intermediate_to_list(v)) if x != {}] -def flatten(xs): +def flatten(elements): """ Flatten a list """ - if xs is not list: - return xs + if elements is not list: + return elements else: - return reduce(lambda x, y: x + flatten(y), xs, []) + return reduce(lambda x, y: x + flatten(y), elements, []) def intermediate_to_list(configuration): @@ -89,7 +87,7 @@ def intermediate_to_list(configuration): ]) >>> [{ - 'spam.eggs' : 'spam and eggs' + 'spam.eggs' : 'spam and eggs' ]} >>> intermediate_to_list([ @@ -109,15 +107,15 @@ def intermediate_to_list(configuration): ]) >>> [ - { - 'spam.eggs' : 'spam and eggs' - }, - { - '1.henry' : 'the first' - }, - { - '2.jacob' : 'the second' - } + { + 'spam.eggs' : 'spam and eggs' + }, + { + '1.henry' : 'the first' + }, + { + '2.jacob' : 'the second' + } ] """ @@ -131,10 +129,9 @@ def intermediate_to_list(configuration): (index, config) = element # update the keys to prefix the index temp = {} - for k, v in config.items(): + for key, value in config.items(): # write the new key - temp['{i}.{key}'.format(i=index + 1, key=k)] = v - + temp['{i}.{key}'.format(i=index + 1, key=key)] = value config = temp # now the keys are updated with the index # reduce to atoms the new config @@ -144,18 +141,17 @@ def intermediate_to_list(configuration): elif isinstance(element, dict): temp = {} - for k, v in element.items(): - if isinstance(v, string_types) or isinstance(v, int): - pass + for key, value in element.items(): + if isinstance(value, string_types) or isinstance(value, int): + temp[key] = value else: # reduce to atom list - # as v could be dict or list + # as value could be dict or list # enclose it in a flattened list - for son in intermediate_to_list(flatten([v])): - - for sk, sv in son.items(): - nested_key = '{key}.{subkey}'.format(key=k, subkey=sk) - temp[nested_key] = sv + for child in intermediate_to_list(flatten([value])): + for child_key, child_value in child.items(): + nested_key = '{key}.{subkey}'.format(key=key, subkey=child_key) + temp[nested_key] = child_value # now it is atomic, append it to result.append(temp) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 61f91da01..c1f90fa6e 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -1,9 +1,8 @@ from copy import deepcopy -from ...utils import get_copy -from ..base.converter import BaseConverter - from ipaddress import ip_interface +from ...utils import get_copy +from ..base.converter import BaseConverter from .wpasupplicant import available_mode_authentication @@ -14,18 +13,17 @@ def status(config, key='disabled'): return 'enabled' -class AirOSConverter(BaseConverter): +class AirOsConverter(BaseConverter): """ Always run the converter from NetJSON to native """ - @classmethod def should_run_forward(cls, config): return True -class Aaa(AirOSConverter): +class Aaa(AirOsConverter): netjson_key = 'general' def wpa2_personal(self): @@ -56,9 +54,8 @@ def is_wpa2_personal(interface): def to_intermediate(self): result = [] - result.append({ - 'status': 'disabled', + 'status': 'disabled', }) result.append([ { @@ -78,177 +75,150 @@ def to_intermediate(self): 'status': 'disabled', } ]) - w = self.wpa2_personal() if w: result.append([w]) - return (('aaa', result),) -class Bridge(AirOSConverter): +class Bridge(AirOsConverter): netjson_key = 'interfaces' def to_intermediate(self): result = [] - original = [ - i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'bridge' - ] - + i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'bridge' + ] bridges = [] for interface in original: bridge_ports = [] for port in interface.get('bridge_members', []): bridge_ports.append({ - 'devname': port, - 'status': 'enabled', + 'devname': port, + 'status': 'enabled', }) - bridges.append({ - 'comment': interface.get('comment', ''), - 'devname': interface['name'], - 'port': bridge_ports, - 'status': status(interface), + 'comment': interface.get('comment', ''), + 'devname': interface['name'], + 'port': bridge_ports, + 'status': status(interface), 'stp': { - 'status': 'enabled', + 'status': 'enabled', } }) result.append(bridges) result.append({ - 'status': 'enabled', + 'status': 'enabled', }) - return (('bridge', result),) -class Discovery(AirOSConverter): +class Discovery(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [ - { - 'cdp': { - 'status': 'enabled', - }, - 'status': 'enabled', + { + 'cdp': { + 'status': 'enabled', }, + 'status': 'enabled', + }, ] return (('discovery', result),) -class Dyndns(AirOSConverter): +class Dyndns(AirOsConverter): netjson_key = 'general' def to_intermediate(self): - result = [ -# [ -# { -# 'servicename': 'dyndns.org', -# }, -# ], - { - 'status': 'disabled', - }, - ] + result = [{'status': 'disabled'}] return (('dyndns', result),) -class Ebtables(AirOSConverter): +class Ebtables(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [ - { - 'sys': { - 'fw': { - 'status': 'disabled', - }, - 'status': 'enabled', + { + 'sys': { + 'fw': { + 'status': 'disabled', }, - 'status': 'enabled', + 'status': 'enabled' }, + 'status': 'enabled' + } ] - return (('ebtables', result),) -class Gui(AirOSConverter): +class Gui(AirOsConverter): netjson_key = 'gui' def to_intermediate(self): result = [ - { - 'language': 'en_US', - }, - { - 'network': { - 'advanced': { - 'status': 'enabled', - } + { + 'language': 'en_US', + }, + { + 'network': { + 'advanced': { + 'status': 'enabled' } } + } ] return (('gui', result),) -class Httpd(AirOSConverter): +class Httpd(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [ - { - 'https': { - 'port': 443, - 'status': 'enabled', - }, - }, - { - 'port': 80, - 'session': { - 'timeout': 900, - }, - 'status': 'enabled', + { + 'https': { + 'port': 443, + 'status': 'enabled', }, + }, + { + 'port': 80, + 'session': {'timeout': 900}, + 'status': 'enabled', + } ] - return (('httpd', result),) -class Igmpproxy(AirOSConverter): +class Igmpproxy(AirOsConverter): netjson_key = 'general' def to_intermediate(self): - result = [ - { - 'status': 'disabled', - }, - ] - + result = [{'status': 'disabled'}] return (('igmpproxy', result),) -class Iptables(AirOSConverter): +class Iptables(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [ - { - 'sys': { - 'portfw': { - 'status': 'disabled', - }, - 'status': 'enabled', - }, - 'status': 'disabled', + { + 'sys': { + 'portfw': {'status': 'disabled'}, + 'status': 'enabled', }, + 'status': 'disabled' + } ] - return (('iptables', result),) -class Netconf(AirOSConverter): +class Netconf(AirOsConverter): netjson_key = 'interfaces' def type_to_role(self, typestr): @@ -270,33 +240,29 @@ def to_intermediate(self): 'up': status(interface), 'mtu': interface.get('mtu', 1500), } - # handle interface type quirks if interface['type'] == 'ethernet' and '.' not in interface['name']: base['autoneg'] = 'enabled' base['flowcontrol'] = { - 'rx': { - 'status': 'enabled', - }, - 'tx': { - 'status': 'enabled', - }, - } + 'rx': { + 'status': 'enabled', + }, + 'tx': { + 'status': 'enabled', + }, + } if interface['type'] == 'wireless': base['devname'] = interface['wireless']['radio'] addresses = interface.get('addresses') - if addresses: # for every address policy put a # configuration for addr in addresses: temp = deepcopy(base) - if addr.get('management'): temp['role'] = self.type_to_role(interface['type']) - # handle explicit address policy if addr['proto'] == 'dhcp': temp['autoip'] = {} @@ -306,69 +272,53 @@ def to_intermediate(self): network = ip_interface(ip_and_mask) temp['ip'] = str(network.ip) temp['netmask'] = str(network.netmask) - interfaces.append(temp) else: # an interface without address # is still valid with these defaults values - base['autoip'] = { - 'status': 'disabled', - } + base['autoip'] = {'status': 'disabled'} interfaces.append(base) - result.append(interfaces) - result.append({ - 'status': 'enabled', - }) + result.append({'status': 'enabled'}) return (('netconf', result),) -class Netmode(AirOSConverter): +class Netmode(AirOsConverter): netjson_key = 'netmode' def to_intermediate(self): result = [] - result.append({ 'status': self.netjson.get('netmode', 'bridge'), }) return (('netmode', result), ) -class Ntpclient(AirOSConverter): +class Ntpclient(AirOsConverter): netjson_key = 'ntp_servers' def to_intermediate(self): result = [] temp = [] - original = get_copy(self.netjson, self.netjson_key, []) - if original: for ntp in original: temp.append({ 'server': ntp, 'status': 'enabled', }) - result.append(temp) - result.append({ - 'status': 'enabled', - }) + result.append({'status': 'enabled'}) else: - result.append({ - 'status': 'disabled', - }) - + result.append({'status': 'disabled'}) return (('ntpclient', result),) -class Pwdog(AirOSConverter): +class Pwdog(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [] - result.append({ 'delay': 300, 'period': 300, @@ -383,11 +333,8 @@ class Radio(BaseConverter): def to_intermediate(self): result = [] - original = get_copy(self.netjson, self.netjson_key, []) - radios = [] - for r in original: radios.append({ 'devname': r['name'], @@ -395,64 +342,48 @@ def to_intermediate(self): 'txpower': r.get('tx_power', ''), 'chanbw': r.get('channel_width', ''), }) - result.append(radios) - - result.append({ - 'status': 'enabled', - }) - + result.append({'status': 'enabled'}) return (('radio', result),) -class Resolv(AirOSConverter): +class Resolv(AirOsConverter): netjson_key = 'dns_servers' def host(self): original = get_copy(self.netjson, 'general', {}) return { - 'host': [{ + 'host': [{ 'name': original.get('hostname', 'airos'), 'status': 'enabled', }], } def nameserver(self): + result = [] original = get_copy(self.netjson, self.netjson_key, []) - - t = [] - for nameserver in original: - t.append({ - 'ip': nameserver, - 'status': 'enabled', + result.append({ + 'ip': nameserver, + 'status': 'enabled', }) - - return {'nameserver': t} + return {'nameserver': result} def to_intermediate(self): result = [] - result.append(self.host()) - result.append(self.nameserver()) - - result.append({ - 'status': 'enabled', - }) - + result.append({'status': 'enabled'}) return (('resolv', result),) -class Route(AirOSConverter): +class Route(AirOsConverter): netjson_key = 'routes' def to_intermediate(self): result = [] original = get_copy(self.netjson, self.netjson_key, []) - routes = [] - for r in original: network = ip_interface(r['destination']) temp = {} @@ -464,47 +395,40 @@ def to_intermediate(self): 'netmask': temp['netmask'], 'status': 'enabled', }) - result.append(routes) - result.append({ - 'status': 'enabled', - }) + result.append({'status': 'enabled'}) return (('route', result),) -class Snmp(AirOSConverter): +class Snmp(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [ - { - 'community': 'public', - 'contact': '', - 'location': '', - 'status': 'enabled', - }, + { + 'community': 'public', + 'contact': '', + 'location': '', + 'status': 'enabled', + }, ] - return (('snmp', result),) -class Sshd(AirOSConverter): +class Sshd(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [] - result.append({ - 'auth': { - 'passwd': 'enabled', - }, + 'auth': {'passwd': 'enabled'}, 'port': 22, 'status': 'enabled', }) return (('sshd', result),) -class Syslog(AirOSConverter): +class Syslog(AirOsConverter): netjson_key = 'general' def to_intermediate(self): @@ -520,12 +444,11 @@ def to_intermediate(self): return (('syslog', result),) -class System(AirOSConverter): +class System(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [] - result.append({ 'airosx': { 'prov': { @@ -546,12 +469,11 @@ def to_intermediate(self): return (('system', result),) -class Telnetd(AirOSConverter): +class Telnetd(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [] - result.append({ 'port': 23, 'status': 'disabled', @@ -559,134 +481,111 @@ def to_intermediate(self): return (('telnetd', result),) -class Tshaper(AirOSConverter): +class Tshaper(AirOsConverter): netjson_key = 'general' def to_intermediate(self): - - return (('tshaper', [{'status': 'disabled', }]),) + return (('tshaper', [{'status': 'disabled'}]),) -class Unms(AirOSConverter): - netjson_keu = 'general' +class Unms(AirOsConverter): + netjson_key = 'general' def to_intermediate(self): - return (('unms', [{'status': 'disabled'}]),) -class Update(AirOSConverter): +class Update(AirOsConverter): netjson_key = 'general' def to_intermediate(self): result = [] - - result.append({ - 'check': { - 'status': 'enabled', - }, - }) + result.append({'check': {'status': 'enabled'}}) return (('update', result),) -class Users(AirOSConverter): - netjson_key = 'general' +class Users(AirOsConverter): + netjson_key = 'user' + + def key_derivation(self): + original = get_copy(self.netjson, self.netjson_key, {}) + return '$1${salt}${derivation}'.format(salt=original['salt'], derivation=original['password']) def to_intermediate(self): result = [] - - result.append({ - 'status': 'disabled', - }) - + original = get_copy(self.netjson, self.netjson_key, {}) + result.append({'status': 'enabled'}) result.append([ { - 'name': 'root', - 'password': 'changeme', - 'status': 'disabled', + 'name': original.get('name'), + 'password': self.key_derivation(), + 'status': 'enabled', }, ]) - return (('users', result),) -class Vlan(AirOSConverter): +class Vlan(AirOsConverter): netjson_key = 'interfaces' def to_intermediate(self): result = [] original = [ - i for i in get_copy(self.netjson, self.netjson_key, []) if '.' in i['name'] - ] - + i for i in get_copy(self.netjson, self.netjson_key, []) if '.' in i['name'] + ] vlans = [] for v in original: vlans.append({ - 'comment': v.get('comment', ''), - 'devname': v['name'].split('.')[0], - 'id': v['name'].split('.')[1], + 'comment': v.get('comment', ''), + 'devname': v['name'].split('.')[0], + 'id': v['name'].split('.')[1], 'status': status(v), }) - result.append(vlans) - result.append({ - 'status': 'enabled', - }) - + result.append({'status': 'enabled'}) return (('vlan', result),) -class Wireless(AirOSConverter): +class Wireless(AirOsConverter): netjson_key = 'interfaces' def to_intermediate(self): result = [] original = [ - i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'wireless' - ] - - ws = [] + i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'wireless' + ] + wireless_list = [] for w in original: - ws.append({ - 'addmtikie': 'enabled', - 'devname': w['wireless']['radio'], + wireless_list.append({ + 'addmtikie': 'enabled', + 'devname': w['wireless']['radio'], 'hide_ssid': 'enabled' if w['wireless'].get('hidden') else 'disabled', 'l2_isolation': 'disabled', 'mac_acl': { 'policy': 'allow', 'status': 'disabled', }, - 'mcast': { - 'enhance': 0, - }, + 'mcast': {'enhance': 0}, 'rate': { 'auto': 'enabled', 'mcs': -1, }, - 'security': { - 'type': 'none', - }, + 'security': {'type': 'none'}, 'signal_led1': 75, 'signal_led2': 50, 'signal_led3': 25, 'signal_led4': 15, 'signal_led_status': 'enabled', - 'ssid': w['wireless']['ssid'], + 'ssid': w['wireless']['ssid'], 'status': status(w), - 'wds': { - 'status': 'enabled', - }, + 'wds': {'status': 'enabled'}, }) - result.append(ws) - - result.append({ - 'status': 'enabled', - }) - + result.append(wireless_list) + result.append({'status': 'enabled'}) return (('wireless', result),) -class Wpasupplicant(AirOSConverter): +class Wpasupplicant(AirOsConverter): netjson_key = 'interfaces' def _station_intermediate(self, original): @@ -713,23 +612,15 @@ def _station_intermediate(self, original): del temp_dev['devname'] result.append({ - 'device': [ - temp_dev, - ], + 'device': [temp_dev], 'profile': [ { 'name': 'AUTO', - 'network': [ - network, - self.secondary_network(), - ], - }, - ], - }) - result.append({ - 'status': 'enabled', + 'network': [network, self.secondary_network()] + } + ] }) - + result.append({'status': 'enabled'}) return (('wpasupplicant', result),) def _access_point_intermediate(self, original): @@ -749,35 +640,22 @@ def _access_point_intermediate(self, original): if original: head = original[0] - if 'encryption' in head: network = ap_auth_protocols.get(head['encryption']['protocol'])(head) - result.append({ - 'status': 'disabled', - }) - + result.append({'status': 'disabled'}) else: network = ap_auth_protocols['none'](head) temp_dev['status'] = 'enabled' - result.append({ - 'status': 'enabled', - }) - + result.append({'status': 'enabled'}) result.append({ - 'device': [ - temp_dev, - ], + 'device': [temp_dev], 'profile': [ { 'name': 'AUTO', - 'network': [ - network, - self.secondary_network(), - ], + 'network': [network, self.secondary_network()], }, ], }) - return (('wpasupplicant', result),) def secondary_network(self): @@ -785,25 +663,17 @@ def secondary_network(self): The default secondary network configuration """ return { - 'key_mgmt': [ - { - 'name': 'NONE', - }, - ], + 'key_mgmt': [{'name': 'NONE'}], 'priority': 2, 'status': 'disabled', } def to_intermediate(self): original = [ - i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'wireless' - ] - + i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'wireless' + ] if original: head = original[0] # call either ``_station_intermediate`` or ``_access_point_intermediate`` # and return the result return getattr(self, '_%s_intermediate' % head['wireless']['mode'])(original) - - -__all__ = [] diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index 676e64bac..aaa9cfd0f 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -1,7 +1,7 @@ from ..base.renderer import BaseRenderer -class AirOS(BaseRenderer): +class AirOsRenderer(BaseRenderer): def cleanup(self, output): stripped = [ diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 13ebccf40..36506315a 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -2,10 +2,8 @@ AirOS specific JSON-Schema definition """ from ...schema import schema as default_schema -from ...schema import DEFAULT_FILE_MODE # noqa - backward compatibility from ...utils import merge_config - """ This defines a new property in the ``Interface``. @@ -33,7 +31,9 @@ "encryption": { "type": "object", "title": "Encryption", - "required": "protocol", + "required": [ + "protocol", + ], "propertyOrder": 20, "oneOf": [ {"$ref": "#/definitions/encryption_none"}, @@ -48,7 +48,9 @@ "encryption": { "type": "object", "title": "Encryption", - "required": "protocol", + "required": [ + "protocol", + ], "propertyOrder": 20, "oneOf": [ {"$ref": "#/definitions/encryption_none"}, @@ -68,6 +70,22 @@ "default": "bridge", "type": "string", }, + "user": { + "additionalProperties": True, + "properties": { + "name": { + "type": "string", + }, + "salt": { + "type": "string", + }, + }, + "required": [ + "name", + "password", + "salt", + ], + }, }, } diff --git a/netjsonconfig/backends/airos/templates/airos.jinja2 b/netjsonconfig/backends/airos/templates/airos.jinja2 index c92fc55ff..9bdf9b2fc 100644 --- a/netjsonconfig/backends/airos/templates/airos.jinja2 +++ b/netjsonconfig/backends/airos/templates/airos.jinja2 @@ -1,13 +1,13 @@ {% for namespace, block in data.items() %} - {% if namespace != 'netmode' %} - {% for element in block %} - {% for k, v in element.items() %} - {{ namespace }}.{{ k }}={{ v }} - {% endfor %} - {% endfor %} - {% else %} - {% for element in block %} - netmode={{ element['status'] }} - {% endfor %} - {% endif %} + {% if namespace != 'netmode' %} + {% for element in block %} + {% for k, v in element.items() %} + {{ namespace }}.{{ k }}={{ v }} + {% endfor %} + {% endfor %} + {% else %} + {% for element in block %} + netmode={{ element['status'] }} + {% endfor %} + {% endif %} {% endfor %} diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index 72a25314f..828ba3762 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -132,6 +132,5 @@ def sta_wpa2_enterprise(interface): 'station': { 'none': sta_no_encryption, 'wpa2_personal': sta_wpa2_personal, -# 'wpa2_enterprise': sta_wpa2_enterprise, }, } diff --git a/tests/airos/dummy.py b/tests/airos/mock.py similarity index 63% rename from tests/airos/dummy.py rename to tests/airos/mock.py index e539982a6..061cf57bc 100644 --- a/tests/airos/dummy.py +++ b/tests/airos/mock.py @@ -1,12 +1,17 @@ -from netjsonconfig import AirOS - -from netjsoncongig.backends.airos.converters import Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, \ - Httpd, Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, \ - Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, System, \ - Telnetd, Update, Users, Vlan, Wireless, Wpasupplicant - from unittest import TestCase +from netjsonconfig import AirOs +from netjsonconfig.backends.airos.converters import (Aaa, Bridge, Discovery, + Dyndns, Ebtables, Gui, + Httpd, Igmpproxy, + Iptables, Netconf, + Netmode, Ntpclient, Pwdog, + Radio, Resolv, Route, + Snmp, Sshd, Syslog, + System, Telnetd, Update, + Users, Vlan, Wireless, + Wpasupplicant) + class ConverterTest(TestCase): """ @@ -33,236 +38,235 @@ def assertEqualConfig(self, a, b): self.assertEqual(a, b) -class AaaAirOS(AirOS): +class AaaAirOs(AirOs): """ Mock backend with converter for radius authentication """ converters = [ - Aaa, + Aaa, ] -class BridgeAirOS(AirOS): +class BridgeAirOs(AirOs): """ Mock backend with converter for bridge interface """ converters = [ - Bridge, + Bridge, ] -class DiscoveryAirOS(AirOS): +class DiscoveryAirOs(AirOs): """ Mock backend with converter for network hardware discovery """ converters = [ - Discovery, + Discovery, ] -class DyndnsAirOS(AirOS): +class DyndnsAirOs(AirOs): """ Mock backend with converter for dynamic dns capabilities """ converters = [ - Dyndns, + Dyndns, ] -class EbtablesAirOS(AirOS): +class EbtablesAirOs(AirOs): """ Mock backend with converter for ebtables """ converters = [ - Ebtables, + Ebtables, ] -class GuiAirOS(AirOS): +class GuiAirOs(AirOs): """ Mock backend with converter for web interface settings """ converters = [ - Gui, + Gui, ] -class HttpdAirOS(AirOS): +class HttpdAirOs(AirOs): """ Mock backend with converter for web server """ converters = [ - Httpd, + Httpd, ] -class Igmpproxy(AirOS): +class Igmpproxy(AirOs): """ Mock backend with converter for igmpproxy """ converters = [ - Igmpproxy, + Igmpproxy, ] -class IptablesAirOS(AirOS): +class IptablesAirOs(AirOs): """ Mock backend with converter for iptables """ converters = [ - Iptables, + Iptables, ] -class NetconfAirOS(AirOS): +class NetconfAirOs(AirOs): """ Mock backend with converter for network configuration """ converters = [ - Netconf, + Netconf, ] -class NetmodeAirOS(AirOS): +class NetmodeAirOs(AirOs): """ Mock backend with converter for network mode """ converters = [ - Netmode, + Netmode, ] -class NtpclientAirOS(AirOS): +class NtpclientAirOs(AirOs): """ Mock backend with converter for ntp settings """ converters = [ - Ntpclient, + Ntpclient, ] -class PwdogAirOS(AirOS): +class PwdogAirOs(AirOs): """ Mock backend with converter for ping watchdog settings """ converters = [ - Pwdog, + Pwdog, ] -class RadioAirOS(AirOS): +class RadioAirOs(AirOs): """ Mock backend with converter for radio settings """ converters = [ - Radio, + Radio, ] -class ResolvAirOS(AirOS): +class ResolvAirOs(AirOs): """ Mock backend with converter for network resolver """ converters = [ - Resolv, + Resolv, ] -class RouteAirOS(AirOS): +class RouteAirOs(AirOs): """ Mock backend with converter for static routes """ converters = [ - Route, + Route, ] -class SnmpAirOS(AirOS): +class SnmpAirOs(AirOs): """ Mock backend with converter for simple network management protocol """ converters = [ - Snmp, + Snmp, ] - -class SshdAirOS(AirOS): +class SshdAirOs(AirOs): """ Mock backend with converter for ssh daemon settings """ converters = [ - Sshd, + Sshd, ] -class SyslogAirOS(AirOS): +class SyslogAirOs(AirOs): """ Mock backend with converter for remote logging """ converters = [ - Syslog, + Syslog, ] -class SystemAirOS(AirOS): +class SystemAirOs(AirOs): """ Mock backend with converter for system settings """ converters = [ - System, + System, ] -class TelnetdAirOS(AirOS): +class TelnetdAirOs(AirOs): """ Mock backend with converter for telnet daemon settings """ converters = [ - Telnetd, + Telnetd, ] -class UpdateAirOS(AirOS): +class UpdateAirOs(AirOs): """ Mock backend with converter for update """ converters = [ - Update, + Update, ] -class UsersAirOS(AirOS): +class UsersAirOs(AirOs): """ Mock backend with converter for users settings """ converters = [ - Users, + Users, ] -class VlanAirOS(AirOS): +class VlanAirOs(AirOs): """ Mock backend with converter for vlan settings """ converters = [ - Vlan, + Vlan, ] -class WirelessAirOS(AirOS): +class WirelessAirOs(AirOs): """ Mock backend with converter for wireless settings """ converters = [ - Wireless, + Wireless, ] -class WpasupplicantAirOS(AirOS): +class WpasupplicantAirOs(AirOs): """ Mock backend with converter for wpasupplicant settings """ converters = [ - Wpasupplicant, + Wpasupplicant, ] diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index c5d4db7b0..8e8e7d3a0 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -1,18 +1,16 @@ -from .dummy import AaaAirOS, ConverterTest +from .mock import AaaAirOs, ConverterTest class TestResolvConverter(ConverterTest): - backend = AaaAirOS + backend = AaaAirOs def test_aaa_key(self): o = self.backend({ "general": {}, "interfaces": [], }) - o.to_intermediate() - expected = [ { 'status': 'disabled', diff --git a/tests/airos/test_bridge.py b/tests/airos/test_bridge.py index 2ca0c717d..f0f1d0668 100644 --- a/tests/airos/test_bridge.py +++ b/tests/airos/test_bridge.py @@ -1,11 +1,11 @@ -from .dummy import BridgeAirOS, ConverterTest +from .mock import BridgeAirOs, ConverterTest class TestBridgeConverter(ConverterTest): """ tests for backends.airos.renderers.SystemRenderer """ - backend = BridgeAirOS + backend = BridgeAirOs def test_active_bridge(self): @@ -32,24 +32,22 @@ def test_active_bridge(self): } ] }) - o.to_intermediate() - expected = [ - { - '1.comment': '', - '1.devname': 'br0', - '1.port.1.devname': 'eth0', - '1.port.1.status': 'enabled', - '1.port.2.devname': 'eth1', - '1.port.2.status': 'enabled', - '1.status': 'enabled', - '1.stp.status': 'enabled' - }, - { - 'status': 'enabled', - } - ] + { + '1.comment': '', + '1.devname': 'br0', + '1.port.1.devname': 'eth0', + '1.port.1.status': 'enabled', + '1.port.2.devname': 'eth1', + '1.port.2.status': 'enabled', + '1.status': 'enabled', + '1.stp.status': 'enabled' + }, + { + 'status': 'enabled', + } + ] self.assertEqualConfig(o.intermediate_data['bridge'], expected) @@ -77,24 +75,22 @@ def test_disabled_bridge(self): } ] }) - o.to_intermediate() - expected = [ - { - '1.comment': '', - '1.devname': 'br0', - '1.port.1.devname': 'eth0', - '1.port.1.status': 'enabled', - '1.port.2.devname': 'eth1', - '1.port.2.status': 'enabled', - '1.status': 'disabled', - '1.stp.status': 'enabled' - }, - { - 'status': 'enabled', - } - ] + { + '1.comment': '', + '1.devname': 'br0', + '1.port.1.devname': 'eth0', + '1.port.1.status': 'enabled', + '1.port.2.devname': 'eth1', + '1.port.2.status': 'enabled', + '1.status': 'disabled', + '1.stp.status': 'enabled' + }, + { + 'status': 'enabled', + } + ] self.assertEqualConfig(o.intermediate_data['bridge'], expected) @@ -141,34 +137,32 @@ def test_many_bridges(self): } ] }) - o.to_intermediate() - expected = [ - { - '1.comment': '', - '1.devname': 'br0', - '1.port.1.devname': 'eth0', - '1.port.1.status': 'enabled', - '1.port.2.devname': 'eth1', - '1.port.2.status': 'enabled', - '1.status': 'disabled', - '1.stp.status': 'enabled' - }, - { - '2.comment': '', - '2.devname': 'br1', - '2.port.1.devname': 'eth2', - '2.port.1.status': 'enabled', - '2.port.2.devname': 'eth3', - '2.port.2.status': 'enabled', - '2.status': 'enabled', - '2.stp.status': 'enabled' - }, - { - 'status': 'enabled', - } - ] + { + '1.comment': '', + '1.devname': 'br0', + '1.port.1.devname': 'eth0', + '1.port.1.status': 'enabled', + '1.port.2.devname': 'eth1', + '1.port.2.status': 'enabled', + '1.status': 'disabled', + '1.stp.status': 'enabled' + }, + { + '2.comment': '', + '2.devname': 'br1', + '2.port.1.devname': 'eth2', + '2.port.1.status': 'enabled', + '2.port.2.devname': 'eth3', + '2.port.2.status': 'enabled', + '2.status': 'enabled', + '2.stp.status': 'enabled' + }, + { + 'status': 'enabled', + } + ] self.assertEqualConfig(o.intermediate_data['bridge'], expected) @@ -187,13 +181,11 @@ def test_no_bridge(self): }, ] }) - o.to_intermediate() - expected = [ - { - 'status': 'enabled', - } - ] + { + 'status': 'enabled', + } + ] self.assertEqualConfig(o.intermediate_data['bridge'], expected) diff --git a/tests/airos/test_discovery.py b/tests/airos/test_discovery.py index 48812f03b..2d7e9e453 100644 --- a/tests/airos/test_discovery.py +++ b/tests/airos/test_discovery.py @@ -1,22 +1,20 @@ -from .dummy import DiscoveryAirOS, ConverterTest +from .mock import ConverterTest, DiscoveryAirOs class TestDiscoveryConverter(ConverterTest): - backend = DiscoveryAirOS + backend = DiscoveryAirOs def test_discovery_key(self): o = self.backend({ "general": {} }) - o.to_intermediate() - expected = [ - { - 'cdp.status': 'enabled', - 'status': 'enabled', - }, + { + 'cdp.status': 'enabled', + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['discovery'], expected) diff --git a/tests/airos/test_dyndns.py b/tests/airos/test_dyndns.py index 2c8148d5f..c45872273 100644 --- a/tests/airos/test_dyndns.py +++ b/tests/airos/test_dyndns.py @@ -1,21 +1,19 @@ -from .dummy import DyndnsAirOS, ConverterTest +from .mock import ConverterTest, DyndnsAirOs class TestDyndnsConverter(ConverterTest): - backend = DyndnsAirOS + backend = DyndnsAirOs def test_Dyndns_key(self): o = self.backend({ "general": {} }) - o.to_intermediate() - expected = [ - { - 'status': 'disabled', - }, + { + 'status': 'disabled', + }, ] self.assertEqualConfig(o.intermediate_data['dyndns'], expected) diff --git a/tests/airos/test_gui.py b/tests/airos/test_gui.py index 43c718eaf..b85e8114a 100644 --- a/tests/airos/test_gui.py +++ b/tests/airos/test_gui.py @@ -1,24 +1,22 @@ -from .dummy import GuiAirOS, ConverterTest +from .mock import ConverterTest, GuiAirOs class TestGuiConverter(ConverterTest): - backend = GuiAirOS + backend = GuiAirOs def test_gui_key(self): o = self.backend({ 'gui': {}, }) - o.to_intermediate() - expected = [ - { - 'language': 'en_US', - }, - { - 'network.advanced.status': 'enabled', - }, + { + 'language': 'en_US', + }, + { + 'network.advanced.status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['gui'], expected) diff --git a/tests/airos/test_httpd.py b/tests/airos/test_httpd.py index 4e0b6696e..178e081f3 100644 --- a/tests/airos/test_httpd.py +++ b/tests/airos/test_httpd.py @@ -1,27 +1,25 @@ -from .dummy import HttpdAirOS, ConverterTest +from .mock import ConverterTest, HttpdAirOs class TestHttpdConverter(ConverterTest): - backend = HttpdAirOS + backend = HttpdAirOs def test_httpd_key(self): o = self.backend({ "general": {} }) - o.to_intermediate() - expected = [ - { - 'https.port': 443, - 'https.status': 'enabled', - }, - { - 'port': 80, - 'session.timeout': 900, - 'status': 'enabled', - }, + { + 'https.port': 443, + 'https.status': 'enabled', + }, + { + 'port': 80, + 'session.timeout': 900, + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['httpd'], expected) diff --git a/tests/airos/test_intermediate.py b/tests/airos/test_intermediate.py new file mode 100644 index 000000000..be5eec412 --- /dev/null +++ b/tests/airos/test_intermediate.py @@ -0,0 +1,20 @@ +import unittest + +from netjsonconfig.backends.airos.airos import intermediate_to_list + + +class TestIntermediateConversion(unittest.TestCase): + + def test_dict_conversion(self): + i = [{'spam': {'eggs': 'spam and eggs'}}] + + o = [{'spam.eggs': 'spam and eggs'}] + + self.assertEqual(intermediate_to_list(i), o) + + def test_list_conversion(self): + i = [{'kings': [{'henry': 'the first'}, {'jacob': 'the second'}]}] + + o = [{'kings.1.henry': 'the first', 'kings.2.jacob': 'the second'}] + + self.assertEqual(intermediate_to_list(i), o) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index c884fbc15..d1291453d 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -1,22 +1,21 @@ from unittest import skip -from .dummy import NetconfAirOS, ConverterTest + +from .mock import ConverterTest, NetconfAirOs class TestNetconfConverter(ConverterTest): - backend = NetconfAirOS + backend = NetconfAirOs def test_netconf_key(self): o = self.backend({ 'interfaces': [] }) - o.to_intermediate() - expected = [ - { - 'status': 'enabled', - }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -32,19 +31,19 @@ def test_active_interface(self): o.to_intermediate() expected = [ - { - '1.autoip.status': 'disabled', - '1.autoneg': 'enabled', - '1.devname': 'eth0', - '1.flowcontrol.tx.status': 'enabled', - '1.flowcontrol.rx.status': 'enabled', - '1.mtu': 1500, - '1.status': 'enabled', - '1.up': 'enabled', - }, - { - 'status': 'enabled', - }, + { + '1.autoip.status': 'disabled', + '1.autoneg': 'enabled', + '1.devname': 'eth0', + '1.flowcontrol.tx.status': 'enabled', + '1.flowcontrol.rx.status': 'enabled', + '1.mtu': 1500, + '1.status': 'enabled', + '1.up': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -59,23 +58,21 @@ def test_inactive_interface(self): } ] }) - o.to_intermediate() - expected = [ - { - '1.autoip.status': 'disabled', - '1.autoneg': 'enabled', - '1.devname': 'eth0', - '1.flowcontrol.tx.status': 'enabled', - '1.flowcontrol.rx.status': 'enabled', - '1.mtu': 1500, - '1.status': 'enabled', - '1.up': 'disabled', - }, - { - 'status': 'enabled', - }, + { + '1.autoip.status': 'disabled', + '1.autoneg': 'enabled', + '1.devname': 'eth0', + '1.flowcontrol.tx.status': 'enabled', + '1.flowcontrol.rx.status': 'enabled', + '1.mtu': 1500, + '1.status': 'enabled', + '1.up': 'disabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -89,20 +86,18 @@ def test_vlan(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'eth0.1', - '1.mtu': 1500, - '1.status': 'enabled', - '1.up': 'enabled', - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'eth0.1', + '1.mtu': 1500, + '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -125,22 +120,20 @@ def test_management_vlan(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'eth0.1', - '1.ip': '192.168.1.20', - '1.netmask': '255.255.255.0', - '1.mtu': 1500, - '1.role': 'mlan', - '1.status': 'enabled', - '1.up': 'enabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'eth0.1', + '1.ip': '192.168.1.20', + '1.netmask': '255.255.255.0', + '1.mtu': 1500, + '1.role': 'mlan', + '1.status': 'enabled', + '1.up': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -159,20 +152,18 @@ def test_access_point(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'ath0', - '1.mtu': 1500, - '1.status': 'enabled', - '1.up': 'enabled', - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'ath0', + '1.mtu': 1500, + '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -192,20 +183,18 @@ def test_station(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'ath0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.mtu': 1500, - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'ath0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -225,20 +214,18 @@ def test_adhoc(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'ath0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.mtu': 1500, - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'ath0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -258,20 +245,18 @@ def test_wds(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'ath0', - '1.mtu': 1500, - '1.up': 'enabled', - '1.status': 'enabled', - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'ath0', + '1.mtu': 1500, + '1.up': 'enabled', + '1.status': 'enabled', + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -291,19 +276,17 @@ def test_monitor(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'ath0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.autoip.status': 'enabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'ath0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -324,19 +307,17 @@ def test_80211s(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'ath0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'ath0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -354,20 +335,18 @@ def test_bridge(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'br0', - '1.status': 'enabled', - '1.up': 'enabled', - '1.mtu': 1500, - '1.autoip.status': 'disabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'br0', + '1.status': 'enabled', + '1.up': 'enabled', + '1.mtu': 1500, + '1.autoip.status': 'disabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -382,17 +361,15 @@ def test_virtual(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'veth0', - '1.status': 'enabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'veth0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -407,17 +384,15 @@ def test_loopback(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'loop0', - '1.status': 'enabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'loop0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -432,17 +407,15 @@ def test_other(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.devname': 'fancyname0', - '1.status': 'enabled', - }, - { - 'status': 'enabled', - }, + { + '1.devname': 'fancyname0', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) @@ -482,48 +455,46 @@ def test_more_interfaces(self): } ], }) - o.to_intermediate() - expected = [ - { - '1.autoip.status': 'disabled', - '1.autoneg': 'enabled', - '1.devname': 'eth0', - '1.flowcontrol.rx.status': 'enabled', - '1.flowcontrol.tx.status': 'enabled', - '1.mtu': 1500, - '1.status': 'enabled', - '1.up': 'enabled', - }, - { - '2.autoip.status': 'disabled', - '2.devname': 'ath0', - '2.mtu': 1500, - '2.status': 'enabled', - '2.up': 'enabled', - }, - { - '3.autoip.status': 'disabled', - '3.devname': 'br0', - '3.mtu': 1500, - '3.status': 'enabled', - '3.up': 'enabled', - }, - { - '4.autoip.status': 'disabled', - '4.devname': 'veth0', - '4.mtu': 1500, - '4.status': 'enabled', - '4.up': 'enabled', - }, - { - '5.autoip.status': 'disabled', - '5.devname': 'loop0', - '5.mtu': 1500, - '5.status': 'enabled', - '5.up': 'enabled', - }, + { + '1.autoip.status': 'disabled', + '1.autoneg': 'enabled', + '1.devname': 'eth0', + '1.flowcontrol.rx.status': 'enabled', + '1.flowcontrol.tx.status': 'enabled', + '1.mtu': 1500, + '1.status': 'enabled', + '1.up': 'enabled', + }, + { + '2.autoip.status': 'disabled', + '2.devname': 'ath0', + '2.mtu': 1500, + '2.status': 'enabled', + '2.up': 'enabled', + }, + { + '3.autoip.status': 'disabled', + '3.devname': 'br0', + '3.mtu': 1500, + '3.status': 'enabled', + '3.up': 'enabled', + }, + { + '4.autoip.status': 'disabled', + '4.devname': 'veth0', + '4.mtu': 1500, + '4.status': 'enabled', + '4.up': 'enabled', + }, + { + '5.autoip.status': 'disabled', + '5.devname': 'loop0', + '5.mtu': 1500, + '5.status': 'enabled', + '5.up': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['netconf'], expected) diff --git a/tests/airos/test_netmode.py b/tests/airos/test_netmode.py index c7579456e..1790e101c 100644 --- a/tests/airos/test_netmode.py +++ b/tests/airos/test_netmode.py @@ -1,20 +1,18 @@ -from .dummy import NetmodeAirOS, ConverterTest +from .mock import ConverterTest, NetmodeAirOs class TestNetmodeConverter(ConverterTest): - backend = NetmodeAirOS + backend = NetmodeAirOs def test_netconf_key(self): o = self.backend({ }) - o.to_intermediate() - expected = [ - { - 'status': 'bridge', - }, + { + 'status': 'bridge', + }, ] self.assertEqualConfig(o.intermediate_data['netmode'], expected) @@ -23,13 +21,11 @@ def test_bridge(self): o = self.backend({ 'netmode': 'bridge', }) - o.to_intermediate() - expected = [ - { - 'status': 'bridge', - }, + { + 'status': 'bridge', + }, ] self.assertEqualConfig(o.intermediate_data['netmode'], expected) @@ -38,13 +34,11 @@ def test_router(self): o = self.backend({ 'netmode': 'router', }) - o.to_intermediate() - expected = [ - { - 'status': 'router', - }, + { + 'status': 'router', + }, ] self.assertEqualConfig(o.intermediate_data['netmode'], expected) diff --git a/tests/airos/test_ntp.py b/tests/airos/test_ntp.py index 433d07200..f66f4dbef 100644 --- a/tests/airos/test_ntp.py +++ b/tests/airos/test_ntp.py @@ -1,21 +1,19 @@ -from .dummy import NtpclientAirOS, ConverterTest +from .mock import ConverterTest, NtpclientAirOs class TestResolvConverter(ConverterTest): - backend = NtpclientAirOS + backend = NtpclientAirOs def test_ntp_key(self): o = self.backend({ "ntp_servers": [], }) - o.to_intermediate() - expected = [ - { - 'status': 'disabled', - }, + { + 'status': 'disabled', + }, ] self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) @@ -24,13 +22,11 @@ def test_no_ntp_server(self): o = self.backend({ "ntp_servers": [], }) - o.to_intermediate() - expected = [ - { - 'status': 'disabled', - }, + { + 'status': 'disabled', + }, ] self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) @@ -41,17 +37,15 @@ def test_single_ntp_server(self): '0.openwrt.pool.ntp.org', ], }) - o.to_intermediate() - expected = [ - { - '1.server': '0.openwrt.pool.ntp.org', - '1.status': 'enabled', - }, - { - 'status': 'enabled', - }, + { + '1.server': '0.openwrt.pool.ntp.org', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) @@ -63,21 +57,19 @@ def test_multiple_ntp_server(self): '1.openwrt.pool.ntp.org', ], }) - o.to_intermediate() - expected = [ - { - '1.server': '0.openwrt.pool.ntp.org', - '1.status': 'enabled', - }, - { - '2.server': '1.openwrt.pool.ntp.org', - '2.status': 'enabled', - }, - { - 'status': 'enabled', - }, + { + '1.server': '0.openwrt.pool.ntp.org', + '1.status': 'enabled', + }, + { + '2.server': '1.openwrt.pool.ntp.org', + '2.status': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) diff --git a/tests/airos/test_pwdog.py b/tests/airos/test_pwdog.py index 0002b8cdf..df616ec78 100644 --- a/tests/airos/test_pwdog.py +++ b/tests/airos/test_pwdog.py @@ -1,24 +1,22 @@ -from .dummy import PwdogAirOS, ConverterTest +from .mock import ConverterTest, PwdogAirOs class TestPwdogConverter(ConverterTest): - backend = PwdogAirOS + backend = PwdogAirOs def test_ntp_key(self): o = self.backend({ "general": {} }) - o.to_intermediate() - expected = [ - { - 'delay': 300, - 'period': 300, - 'retry': 3, - 'status': 'enabled', - }, + { + 'delay': 300, + 'period': 300, + 'retry': 3, + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['pwdog'], expected) diff --git a/tests/airos/test_radio.py b/tests/airos/test_radio.py index fd62570a7..252575d76 100644 --- a/tests/airos/test_radio.py +++ b/tests/airos/test_radio.py @@ -1,21 +1,19 @@ -from .dummy import RadioAirOS, ConverterTest +from .mock import ConverterTest, RadioAirOs class TestRadioConverter(ConverterTest): - backend = RadioAirOS + backend = RadioAirOs def test_no_radio(self): o = self.backend({ "radios": [] }) - o.to_intermediate() - expected = [ - { - 'status': 'enabled', - }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['radio'], expected) @@ -32,19 +30,17 @@ def test_active_radio(self): } ] }) - o.to_intermediate() - expected = [ - { - '1.chanbw': 20, - '1.devname': 'ath0', - '1.status': 'enabled', - '1.txpower': '', - }, - { - 'status': 'enabled', - }, + { + '1.chanbw': 20, + '1.devname': 'ath0', + '1.status': 'enabled', + '1.txpower': '', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['radio'], expected) @@ -61,19 +57,17 @@ def test_inactive_radio(self): } ] }) - o.to_intermediate() - expected = [ - { - '1.chanbw': 20, - '1.devname': 'ath0', - '1.status': 'disabled', - '1.txpower': '', - }, - { - 'status': 'enabled', - }, + { + '1.chanbw': 20, + '1.devname': 'ath0', + '1.status': 'disabled', + '1.txpower': '', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['radio'], expected) diff --git a/tests/airos/test_resolv.py b/tests/airos/test_resolv.py index 3e1ee86a6..e837402ac 100644 --- a/tests/airos/test_resolv.py +++ b/tests/airos/test_resolv.py @@ -1,9 +1,9 @@ -from .dummy import ResolvAirOS, ConverterTest +from .mock import ConverterTest, ResolvAirOs class TestResolvConverter(ConverterTest): - backend = ResolvAirOS + backend = ResolvAirOs def test_resolv(self): o = self.backend({ @@ -11,21 +11,19 @@ def test_resolv(self): "10.150.42.1" ], }) - o.to_intermediate() - expected = [ - { - 'host.1.name': 'airos', - 'host.1.status': 'enabled', - }, - { - 'nameserver.1.ip': '10.150.42.1', - 'nameserver.1.status': 'enabled', - }, - { - 'status': 'enabled', - }, + { + 'host.1.name': 'airos', + 'host.1.status': 'enabled', + }, + { + 'nameserver.1.ip': '10.150.42.1', + 'nameserver.1.status': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['resolv'], expected) @@ -34,17 +32,15 @@ def test_no_dns_server(self): o = self.backend({ "dns_servers": [], }) - o.to_intermediate() - expected = [ - { - 'host.1.name': 'airos', - 'host.1.status': 'enabled', - }, - { - 'status': 'enabled', - }, + { + 'host.1.name': 'airos', + 'host.1.status': 'enabled', + }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['resolv'], expected) diff --git a/tests/airos/test_vlan.py b/tests/airos/test_vlan.py index 935bf0003..4c6e51a57 100644 --- a/tests/airos/test_vlan.py +++ b/tests/airos/test_vlan.py @@ -1,14 +1,13 @@ -from .dummy import VlanAirOS, ConverterTest +from .mock import ConverterTest, VlanAirOs class TestVlanConverter(ConverterTest): """ tests for backends.airos.renderers.SystemRenderer """ - backend = VlanAirOS + backend = VlanAirOs def test_active_vlan(self): - o = self.backend({ "interfaces": [ { @@ -18,25 +17,21 @@ def test_active_vlan(self): } ] }) - o.to_intermediate() - expected = [ - { - '1.comment': '', - '1.devname': 'eth0', - '1.id': '1', - '1.status': 'enabled', - }, - { - 'status': 'enabled', - } - ] - + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + } + ] self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_disabled_vlan(self): - o = self.backend({ "interfaces": [ { @@ -46,25 +41,21 @@ def test_disabled_vlan(self): } ] }) - expected = [ - { - '1.comment': '', - '1.devname': 'eth0', - '1.id': '1', - '1.status': 'disabled', - }, - { - 'status': 'enabled', - } - ] - + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status': 'disabled', + }, + { + 'status': 'enabled', + } + ] o.to_intermediate() - self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_many_vlan(self): - o = self.backend({ "interfaces": [ { @@ -79,31 +70,27 @@ def test_many_vlan(self): } ] }) - expected = [ - { - '1.comment': '', - '1.devname': 'eth0', - '1.id': '1', - '1.status': 'enabled', - }, - { - '2.comment': '', - '2.devname': 'eth0', - '2.id': '2', - '2.status': 'enabled', - }, - { - 'status': 'enabled', - } - ] - + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status': 'enabled', + }, + { + '2.comment': '', + '2.devname': 'eth0', + '2.id': '2', + '2.status': 'enabled', + }, + { + 'status': 'enabled', + } + ] o.to_intermediate() - self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_mixed_vlan(self): - o = self.backend({ "interfaces": [ { @@ -118,31 +105,27 @@ def test_mixed_vlan(self): } ] }) - expected = [ - { - '1.comment': '', - '1.devname': 'eth0', - '1.id': '1', - '1.status': 'disabled', - }, - { - '2.comment': '', - '2.devname': 'eth0', - '2.id': '2', - '2.status': 'enabled', - }, - { - 'status': 'enabled', - } - ] - + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status': 'disabled', + }, + { + '2.comment': '', + '2.devname': 'eth0', + '2.id': '2', + '2.status': 'enabled', + }, + { + 'status': 'enabled', + } + ] o.to_intermediate() - self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_no_vlan(self): - o = self.backend({ "interfaces": [ { @@ -152,19 +135,15 @@ def test_no_vlan(self): }, ] }) - expected = [ - { - 'status': 'enabled', - }, - ] - + { + 'status': 'enabled', + }, + ] o.to_intermediate() - self.assertEqualConfig(o.intermediate_data['vlan'], expected) def test_one_vlan(self): - o = self.backend({ "interfaces": [ { @@ -180,19 +159,16 @@ def test_one_vlan(self): ] }) - expected = [ - { - '1.comment': '', - '1.devname': 'eth0', - '1.id': '1', - '1.status': 'enabled', - }, - { - 'status': 'enabled', - }, - ] - + { + '1.comment': '', + '1.devname': 'eth0', + '1.id': '1', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] o.to_intermediate() - self.assertEqualConfig(o.intermediate_data['vlan'], expected) diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index 12663234f..c62f96643 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -1,9 +1,9 @@ -from .dummy import WirelessAirOS, ConverterTest +from .mock import ConverterTest, WirelessAirOs class TestWirelessConverter(ConverterTest): - backend = WirelessAirOS + backend = WirelessAirOs def test_active_wireless(self): @@ -32,35 +32,33 @@ def test_active_wireless(self): } ] }) - o.to_intermediate() - expected = [ - { - '1.addmtikie': 'enabled', - '1.devname': 'radio0', - '1.hide_ssid': 'disabled', - '1.l2_isolation': 'disabled', - '1.mac_acl.policy': 'allow', - '1.mac_acl.status': 'disabled', - '1.mcast.enhance': 0, - '1.rate.auto': 'enabled', - '1.rate.mcs': -1, - '1.security.type': 'none', - '1.signal_led1': 75, - '1.signal_led2': 50, - '1.signal_led3': 25, - '1.signal_led4': 15, - '1.signal_led_status': 'enabled', - '1.ssid': 'ap-ssid-example', - '1.status': 'enabled', - '1.wds.status': 'enabled', + { + '1.addmtikie': 'enabled', + '1.devname': 'radio0', + '1.hide_ssid': 'disabled', + '1.l2_isolation': 'disabled', + '1.mac_acl.policy': 'allow', + '1.mac_acl.status': 'disabled', + '1.mcast.enhance': 0, + '1.rate.auto': 'enabled', + '1.rate.mcs': -1, + '1.security.type': 'none', + '1.signal_led1': 75, + '1.signal_led2': 50, + '1.signal_led3': 25, + '1.signal_led4': 15, + '1.signal_led_status': 'enabled', + '1.ssid': 'ap-ssid-example', + '1.status': 'enabled', + '1.wds.status': 'enabled', - }, - { - 'status': 'enabled', - } - ] + }, + { + 'status': 'enabled', + } + ] self.assertEqualConfig(o.intermediate_data['wireless'], expected) @@ -92,33 +90,31 @@ def test_inactive_wireless(self): } ] }) - o.to_intermediate() - expected = [ - { - '1.addmtikie': 'enabled', - '1.devname': 'radio0', - '1.hide_ssid': 'disabled', - '1.l2_isolation': 'disabled', - '1.mac_acl.policy': 'allow', - '1.mac_acl.status': 'disabled', - '1.mcast.enhance': 0, - '1.rate.auto': 'enabled', - '1.rate.mcs': -1, - '1.security.type': 'none', - '1.signal_led1': 75, - '1.signal_led2': 50, - '1.signal_led3': 25, - '1.signal_led4': 15, - '1.signal_led_status': 'enabled', - '1.ssid': 'ap-ssid-example', - '1.status': 'disabled', - '1.wds.status': 'enabled', - }, - { - 'status': 'enabled', - } - ] + { + '1.addmtikie': 'enabled', + '1.devname': 'radio0', + '1.hide_ssid': 'disabled', + '1.l2_isolation': 'disabled', + '1.mac_acl.policy': 'allow', + '1.mac_acl.status': 'disabled', + '1.mcast.enhance': 0, + '1.rate.auto': 'enabled', + '1.rate.mcs': -1, + '1.security.type': 'none', + '1.signal_led1': 75, + '1.signal_led2': 50, + '1.signal_led3': 25, + '1.signal_led4': 15, + '1.signal_led_status': 'enabled', + '1.ssid': 'ap-ssid-example', + '1.status': 'disabled', + '1.wds.status': 'enabled', + }, + { + 'status': 'enabled', + } + ] self.assertEqualConfig(o.intermediate_data['wireless'], expected) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 502e5d636..00665a5bf 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -2,7 +2,7 @@ from netjsonconfig.exceptions import ValidationError -from .dummy import WpasupplicantAirOS, ConverterTest +from .mock import ConverterTest, WpasupplicantAirOs class TestWpasupplicantStation(ConverterTest): @@ -10,10 +10,9 @@ class TestWpasupplicantStation(ConverterTest): Test the wpasupplicant converter for a device in ``station`` mode """ - backend = WpasupplicantAirOS + backend = WpasupplicantAirOs def test_invalid_encryption(self): - o = self.backend({ "interfaces": [ { @@ -38,7 +37,6 @@ def test_invalid_encryption(self): o.validate() def test_no_encryption(self): - o = self.backend({ "interfaces": [ { @@ -54,36 +52,29 @@ def test_no_encryption(self): "ssid": "ap-ssid-example", "bssid": "00:11:22:33:44:55", }, -# "encryption": { -# "protocol": "none", -# }, } ] }) - o.to_intermediate() - expected = [ - { - 'device.1.profile': 'AUTO', - 'device.1.status': 'enabled', - 'profile.1.name': 'AUTO', - 'profile.1.network.1.ssid': 'ap-ssid-example', - 'profile.1.network.1.priority': 100, - 'profile.1.network.1.key_mgmt.1.name': 'NONE', - 'profile.1.network.2.key_mgmt.1.name': 'NONE', - 'profile.1.network.2.priority': 2, - 'profile.1.network.2.status': 'disabled', - }, - { - 'status': 'enabled', - } - ] - + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + { + 'status': 'enabled', + } + ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) def test_wpa2_personal(self): - o = self.backend({ "interfaces": [ { @@ -106,38 +97,34 @@ def test_wpa2_personal(self): } ] }) - o.to_intermediate() - expected = [ - { - 'device.1.profile': 'AUTO', - 'device.1.status': 'enabled', - 'device.1.driver': 'madwifi', - 'device.1.devname': 'radio0', - 'profile.1.name': 'AUTO', - 'profile.1.network.1.phase2=auth': 'MSCHAPV2', - 'profile.1.network.1.eap.1.status': 'disabled', - 'profile.1.network.1.psk': 'cucumber', - 'profile.1.network.1.pairwise.1.name': 'CCMP', - 'profile.1.network.1.proto.1.name': 'RSN', - 'profile.1.network.1.ssid': 'ap-ssid-example', - 'profile.1.network.1.priority': 100, - 'profile.1.network.1.key_mgmt.1.name': 'WPA-PSK', - 'profile.1.network.2.key_mgmt.1.name': 'NONE', - 'profile.1.network.2.priority': 2, - 'profile.1.network.2.status': 'disabled', - }, - { - 'status': 'enabled', - }, - ] - + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'device.1.driver': 'madwifi', + 'device.1.devname': 'radio0', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.phase2=auth': 'MSCHAPV2', + 'profile.1.network.1.eap.1.status': 'disabled', + 'profile.1.network.1.psk': 'cucumber', + 'profile.1.network.1.pairwise.1.name': 'CCMP', + 'profile.1.network.1.proto.1.name': 'RSN', + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.key_mgmt.1.name': 'WPA-PSK', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) @skip("target wpa2_enterprise later") def test_wpa2_enterprise(self): - o = self.backend({ "interfaces": [ { @@ -159,37 +146,34 @@ def test_wpa2_enterprise(self): } ] }) - o.to_intermediate() - expected = [ - { - 'status': 'enabled', - }, - { - 'device.1.profile': 'AUTO', - 'device.1.status': 'enabled', - 'device.1.driver': 'madwifi', - 'device.1.devname': 'radio0', - 'profile.1.name': 'AUTO', - 'profile.1.network.1.phase2=auth': 'MSCHAPV2', - 'profile.1.network.1.eap.1.status': 'enabled', - 'profile.1.network.1.eap.1.name': 'TTLS', - 'profile.1.network.1.password': 'TODO', - 'profile.1.network.1.identity': 'TODO', - 'profile.1.network.1.anonymous_identity': 'TODO', - 'profile.1.network.1.psk': 'cucumber', - 'profile.1.network.1.pairwise.1.name': 'CCMP', - 'profile.1.network.1.proto.1.name': 'RSN', - 'profile.1.network.1.ssid': 'ap-ssid-example', - 'profile.1.network.1.priority': 100, - 'profile.1.network.1.key_mgmt.1.name': 'WPA-EAP', - 'profile.1.network.2.key_mgmt.1.name': 'NONE', - 'profile.1.network.2.priority': 2, - 'profile.1.network.2.status': 'disabled', - }, - ] - + { + 'status': 'enabled', + }, + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'device.1.driver': 'madwifi', + 'device.1.devname': 'radio0', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.phase2=auth': 'MSCHAPV2', + 'profile.1.network.1.eap.1.status': 'enabled', + 'profile.1.network.1.eap.1.name': 'TTLS', + 'profile.1.network.1.password': 'TODO', + 'profile.1.network.1.identity': 'TODO', + 'profile.1.network.1.anonymous_identity': 'TODO', + 'profile.1.network.1.psk': 'cucumber', + 'profile.1.network.1.pairwise.1.name': 'CCMP', + 'profile.1.network.1.proto.1.name': 'RSN', + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.key_mgmt.1.name': 'WPA-EAP', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) @@ -198,11 +182,9 @@ class TestWpasupplicantAccess(ConverterTest): Test the wpasupplicant converter for a device in ``access_point`` mode """ - - backend = WpasupplicantAirOS + backend = WpasupplicantAirOs def test_no_encryption(self): - o = self.backend({ "interfaces": [ { @@ -217,32 +199,26 @@ def test_no_encryption(self): "mode": "access_point", "ssid": "ap-ssid-example", }, -# "encryption": { -# "protocol": "none", -# }, }, ] }) - o.to_intermediate() - expected = [ - { - 'status': 'enabled', - }, - { - 'device.1.profile': 'AUTO', - 'device.1.status': 'enabled', - 'profile.1.name': 'AUTO', - 'profile.1.network.1.priority': 100, - 'profile.1.network.1.ssid': 'ap-ssid-example', - 'profile.1.network.1.key_mgmt.1.name': 'NONE', - 'profile.1.network.2.key_mgmt.1.name': 'NONE', - 'profile.1.network.2.priority': 2, - 'profile.1.network.2.status': 'disabled', - }, - ] - + { + 'status': 'enabled', + }, + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) def test_wpa2_personal(self): @@ -268,25 +244,22 @@ def test_wpa2_personal(self): } ] }) - o.to_intermediate() - expected = [ - { - 'status': 'disabled', - }, - { - 'device.1.profile': 'AUTO', - 'device.1.status': 'disabled', - 'profile.1.name': 'AUTO', - 'profile.1.network.1.priority': 100, - 'profile.1.network.1.ssid': 'ap-ssid-example', - 'profile.1.network.1.psk': 'cucumber', - 'profile.1.network.1.key_mgmt.1.name': 'NONE', - 'profile.1.network.2.key_mgmt.1.name': 'NONE', - 'profile.1.network.2.priority': 2, - 'profile.1.network.2.status': 'disabled', - }, - ] - + { + 'status': 'disabled', + }, + { + 'device.1.profile': 'AUTO', + 'device.1.status': 'disabled', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.1.psk': 'cucumber', + 'profile.1.network.1.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + } + ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) From caecb574d5f19693f8fbc0c38bad30ed50ca7a14 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 18 Jul 2017 13:44:31 +0200 Subject: [PATCH 158/342] [airos] simplify conversion from intermediate to renderer data --- netjsonconfig/backends/airos/airos.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 36236dcde..cee393f76 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -122,25 +122,14 @@ def intermediate_to_list(configuration): result = [] for element in configuration: + temp = {} if isinstance(element, list): - result = result + intermediate_to_list(list(enumerate(element))) - - elif isinstance(element, tuple): - (index, config) = element - # update the keys to prefix the index - temp = {} - for key, value in config.items(): - # write the new key - temp['{i}.{key}'.format(i=index + 1, key=key)] = value - config = temp - # now the keys are updated with the index - # reduce to atoms the new config - # by recursively calling yourself - # on a list containing the new atom - result = result + intermediate_to_list([config]) + for index, el in enumerate(element): + for key, value in el.items(): + temp['{i}.{key}'.format(i=index + 1, key=key)] = value + result = result + intermediate_to_list([temp]) elif isinstance(element, dict): - temp = {} for key, value in element.items(): if isinstance(value, string_types) or isinstance(value, int): temp[key] = value From cace69719a0ed38418fc31951449d94c49b8eb72 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 20 Jul 2017 11:29:28 +0200 Subject: [PATCH 159/342] [airos][test] added test for multiple elements in intermediate representation --- tests/airos/test_intermediate.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/airos/test_intermediate.py b/tests/airos/test_intermediate.py index be5eec412..0b78d2252 100644 --- a/tests/airos/test_intermediate.py +++ b/tests/airos/test_intermediate.py @@ -18,3 +18,16 @@ def test_list_conversion(self): o = [{'kings.1.henry': 'the first', 'kings.2.jacob': 'the second'}] self.assertEqual(intermediate_to_list(i), o) + + def test_multiple_conversion(self): + i = [ + {'snakes': {'loved': 'yes'}}, + {'dogs': {'loved': 'yes'}}, + ] + + o = [ + {'snakes.loved': 'yes'}, + {'dogs.loved': 'yes'}, + ] + + self.assertEqual(intermediate_to_list(i), o) From 79c1c755adf08273389c5e3f70d487d1dedaf6ed Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 20 Jul 2017 12:54:24 +0200 Subject: [PATCH 160/342] [airos] moved conversion functions to separate module --- netjsonconfig/backends/airos/airos.py | 97 +-------------------------- 1 file changed, 1 insertion(+), 96 deletions(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index cee393f76..71f901c0f 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -1,12 +1,10 @@ -from six import string_types -from six.moves import reduce - from ..base.backend import BaseBackend from .converters import (Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, Httpd, Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, System, Telnetd, Update, Users, Vlan, Wireless, Wpasupplicant) +from .intermediate import flatten, intermediate_to_list from .renderers import AirOsRenderer from .schema import schema @@ -56,96 +54,3 @@ def to_intermediate(self): super(AirOs, self).to_intermediate() for k, v in self.intermediate_data.items(): self.intermediate_data[k] = [x for x in flatten(intermediate_to_list(v)) if x != {}] - - -def flatten(elements): - """ - Flatten a list - """ - if elements is not list: - return elements - else: - return reduce(lambda x, y: x + flatten(y), elements, []) - - -def intermediate_to_list(configuration): - """ - Explore the configuration tree and flatten where - possible with the following policy - - list -> prepend the list index to every item key - - dictionary -> prepend the father key to every key - - configuration :: List[Enum[Dict,List]] - return List[Dict] - - >>> intermediate_to_list([ - { - 'spam': { - 'eggs': 'spam and eggs' - } - } - ]) - >>> - [{ - 'spam.eggs' : 'spam and eggs' - ]} - - >>> intermediate_to_list([ - { - 'spam': { - 'eggs': 'spam and eggs' - } - }, - [ - { - 'henry': 'the first' - }, - { - 'jacob' : 'the second' - } - ] - ]) - >>> - [ - { - 'spam.eggs' : 'spam and eggs' - }, - { - '1.henry' : 'the first' - }, - { - '2.jacob' : 'the second' - } - ] - """ - - result = [] - - for element in configuration: - temp = {} - if isinstance(element, list): - for index, el in enumerate(element): - for key, value in el.items(): - temp['{i}.{key}'.format(i=index + 1, key=key)] = value - result = result + intermediate_to_list([temp]) - - elif isinstance(element, dict): - for key, value in element.items(): - if isinstance(value, string_types) or isinstance(value, int): - temp[key] = value - else: - # reduce to atom list - # as value could be dict or list - # enclose it in a flattened list - for child in intermediate_to_list(flatten([value])): - for child_key, child_value in child.items(): - nested_key = '{key}.{subkey}'.format(key=key, subkey=child_key) - temp[nested_key] = child_value - - # now it is atomic, append it to - result.append(temp) - - else: - raise Exception('malformed intermediate representation') - - return result From 4cd41ded6154fb147d0724ab6728a86994f9fcf0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 20 Jul 2017 12:57:03 +0200 Subject: [PATCH 161/342] [test] update import for intermediate conversion test --- netjsonconfig/backends/airos/intermediate.py | 105 +++++++++++++++++++ tests/airos/test_intermediate.py | 2 +- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 netjsonconfig/backends/airos/intermediate.py diff --git a/netjsonconfig/backends/airos/intermediate.py b/netjsonconfig/backends/airos/intermediate.py new file mode 100644 index 000000000..c44052368 --- /dev/null +++ b/netjsonconfig/backends/airos/intermediate.py @@ -0,0 +1,105 @@ +from six import string_types +from six.moves import reduce + + +def flatten(elements): + """ + Flatten a list + elements :: List + return List + """ + if elements is not list: + return elements + else: + return reduce(lambda x, y: x + flatten(y), elements, []) + + +def shrink(configuration): + """ + configuration :: Dict + return Dict + """ + temp = {} + for key, value in configuration.items(): + if isinstance(value, string_types) or isinstance(value, int): + temp[key] = value + else: + # reduce to atom list + # as value could be dict or list + # enclose it in a flattened list + for child in intermediate_to_list(flatten([value])): + for child_key, child_value in child.items(): + nested_key = '{key}.{subkey}'.format(key=key, subkey=child_key) + temp[nested_key] = child_value + return temp + + +def intermediate_to_list(configuration): + """ + Explore the configuration tree and flatten where + possible with the following policy + - list -> prepend the list index to every item key + - dictionary -> prepend the father key to every key + + configuration :: List[Enum[Dict,List]] + return List[Dict] + + >>> intermediate_to_list([ + { + 'spam': { + 'eggs': 'spam and eggs' + } + } + ]) + >>> + [{ + 'spam.eggs' : 'spam and eggs' + ]} + + >>> intermediate_to_list([ + { + 'spam': { + 'eggs': 'spam and eggs' + } + }, + [ + { + 'henry': 'the first' + }, + { + 'jacob' : 'the second' + } + ] + ]) + >>> + [ + { + 'spam.eggs' : 'spam and eggs' + }, + { + '1.henry' : 'the first' + }, + { + '2.jacob' : 'the second' + } + ] + """ + + result = [] + + for element in configuration: + temp = {} + if isinstance(element, list): + for index, el in enumerate(element): + for key, value in el.items(): + temp['{i}.{key}'.format(i=index + 1, key=key)] = value + result = result + intermediate_to_list([temp]) + + elif isinstance(element, dict): + temp.update(shrink(element)) + result.append(temp) + + else: + raise Exception('malformed intermediate representation') + + return result diff --git a/tests/airos/test_intermediate.py b/tests/airos/test_intermediate.py index 0b78d2252..d8bcabe13 100644 --- a/tests/airos/test_intermediate.py +++ b/tests/airos/test_intermediate.py @@ -1,6 +1,6 @@ import unittest -from netjsonconfig.backends.airos.airos import intermediate_to_list +from netjsonconfig.backends.airos.intermediate import intermediate_to_list class TestIntermediateConversion(unittest.TestCase): From f7cd4bcd90510e9756fb4fe6d46ca96bf25fa98b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 20 Jul 2017 13:03:14 +0200 Subject: [PATCH 162/342] [airos] fixed broken tests --- netjsonconfig/backends/airos/intermediate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/intermediate.py b/netjsonconfig/backends/airos/intermediate.py index c44052368..2320b6008 100644 --- a/netjsonconfig/backends/airos/intermediate.py +++ b/netjsonconfig/backends/airos/intermediate.py @@ -88,14 +88,15 @@ def intermediate_to_list(configuration): result = [] for element in configuration: - temp = {} if isinstance(element, list): for index, el in enumerate(element): + temp = {} for key, value in el.items(): temp['{i}.{key}'.format(i=index + 1, key=key)] = value - result = result + intermediate_to_list([temp]) + result = result + intermediate_to_list([temp]) elif isinstance(element, dict): + temp = {} temp.update(shrink(element)) result.append(temp) From bdd19ec894a1c2df746bcf2e33052439e890846c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 20 Jul 2017 16:53:29 +0200 Subject: [PATCH 163/342] [airos] added tests for wireless authentication in aaa section --- tests/airos/test_aaa.py | 135 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index 8e8e7d3a0..d9f3d2dc8 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -12,9 +12,142 @@ def test_aaa_key(self): }) o.to_intermediate() expected = [ + { + 'status': 'disabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['aaa'], expected) + + def test_ap_no_authentication(self): + o = self.backend({ + "interfaces": [ { - 'status': 'disabled', + "type": "wireless", + "name": "wlan0", + "wireless": { + "mode": "access_point", + "radio": "ath0", + "ssid": "i-like-pasta", + }, }, + ], + }) + o.to_intermediate() + expected = [ + { + 'status': 'disabled', + }, + { + '1.radius.acct.1.port': 1813, + '1.radius.acct.1.status': 'disabled', + '1.radius.auth.1.port': 1812, + '1.radius.auth.1.status': 'disabled', + '1.status': 'disabled', + }, ] + self.assertEqualConfig(o.intermediate_data['aaa'], expected) + + def test_ap_psk_authentication(self): + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "wireless": { + "mode": "access_point", + "radio": "ath0", + "ssid": "i-like-pasta", + "encryption": { + "proto": "wpa2_personal", + "key": "and-pizza-too", + }, + }, + }, + ], + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + '1.radius.acct.1.port': 1813, + '1.radius.acct.1.status': 'disabled', + '1.radius.auth.1.port': 1812, + '1.radius.auth.1.status': 'disabled', + '1.status': 'enabled', + # here begins magic + '1.radius.macacl.status': 'disabled', # move to radius method + '1.ssid': 'i-like-pasta', + '1.br.devname': 'br0', # only in bridge mode? + '1.devname': 'ath0', + '1.driver': 'madwifi', + '1.wpa.1.pairwise': 'CCMP', + '1.wpa.key.1.mgmt': 'WPA-PSK', + '1.wpa.mode': 2, + '1.wpa.psk': 'and-pizza-too', + } + ] + self.assertEqualConfig(o.intermediate_data['aaa'], expected) + def test_sta_no_authentication(self): + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "wireless": { + "mode": "station", + "radio": "ath0", + "ssid": "i-like-pasta", + }, + }, + ], + }) + o.to_intermediate() + expected = [ + { + 'status': 'disabled', + }, + { + '1.radius.acct.1.port': 1813, + '1.radius.acct.1.status': 'disabled', + '1.radius.auth.1.port': 1812, + '1.radius.auth.1.status': 'disabled', + '1.status': 'disabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['aaa'], expected) + + def test_sta_psk_authentication(self): + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "wireless": { + "mode": "station", + "radio": "ath0", + "ssid": "i-like-pasta", + "encryption": { + "proto": "wpa2_personal", + "key": "and-pizza-too", + }, + }, + }, + ], + }) + o.to_intermediate() + expected = [ + { + 'status': 'disabled', + }, + { + '1.radius.acct.1.port': 1813, + '1.radius.acct.1.status': 'disabled', + '1.radius.auth.1.port': 1812, + '1.radius.auth.1.status': 'disabled', + '1.status': 'disabled', + }, + ] self.assertEqualConfig(o.intermediate_data['aaa'], expected) From a84dee97fc07fa54aae5a67b1c5dabc945fbc12a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 20 Jul 2017 17:46:25 +0200 Subject: [PATCH 164/342] [airos] draft for aaa section update --- netjsonconfig/backends/airos/converters.py | 112 ++++++++++++++------- 1 file changed, 74 insertions(+), 38 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index c1f90fa6e..bbe40eaf1 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -13,6 +13,26 @@ def status(config, key='disabled'): return 'enabled' +def get_psk(interface): + t = { + 'wpa': { + 'psk': interface['encryption']['key'], + }, + } + return t + + +def is_wpa2_personal(interface): + """ + returns True if the interface is configured with wpa2_personal + authentication + """ + try: + return interface['encryption']['protocol'] == 'wpa2_personal' + except: + return False + + class AirOsConverter(BaseConverter): """ Always run the converter from NetJSON @@ -28,53 +48,69 @@ class Aaa(AirOsConverter): def wpa2_personal(self): """ - When using wpa_personal the wifi password is written - in ``aaa.1.wpa.psk`` instead of ``wpasupplicant`` + When using wpa2_personal the wifi password is written + in ``aaa.1.wpa.psk`` too """ - wireless = [i for i in get_copy(self.netjson, 'interfaces', []) if i['type'] == 'wireless'] - - def get_psk(interface): - t = { - 'wpa': { - 'psk': interface['encryption']['key'], - }, - } - return t - - def is_wpa2_personal(interface): - try: - return interface['encryption']['protocol'] == 'wpa2_personal' - except: - return False - try: - return [get_psk(i) for i in wireless if is_wpa2_personal(i)][0] + return [get_psk(i) for i in self.wireless() if is_wpa2_personal(i)][0] except IndexError: return {} + def wireless(self): + """ + Return all the wireless interfaces + """ + return [i for i in get_copy(self.netjson, 'interfaces', []) if i['type'] == 'wireless'] + + def status(self): + """ + The aaa.status value is enabled when the interface is in access_point mode + with wpa2_personal authentication + """ + t = self.wireless()[0]['wireless'] + if t['mode'] == 'access_point' and t['encryption']['protocol'] == 'wpa2_personal': + return 'enabled' + else: + return 'disabled' + + def ap_psk(self): + t = self.wireless()[0]['wireless'] + temp = { + 'radius.macacl.status': 'disabled', + 'ssid': t['ssid'], + 'devname': t['radio'], + 'driver': 'madwifi', + 'wpa.1.pairwise': 'CCMP', + 'wpa.key.1rmgmt': 'WPA-PSK', + 'wpa.mode': 2, + } + if t['mode'] == 'access_point' and t['encryption']['protocol'] == 'wpa2_personal': + return temp + else: + return {} + def to_intermediate(self): result = [] - result.append({ + temp = { + 'radius': { + 'acct': [ + { + 'port': 1813, + 'status': 'disabled', + }, + ], + 'auth': [ + { + 'port': 1812, + }, + ], + }, 'status': 'disabled', + } + result.append({ + 'status': self.status(), }) - result.append([ - { - 'radius': { - 'acct': [ - { - 'port': 1813, - 'status': 'disabled', - }, - ], - 'auth': [ - { - 'port': 1812, - }, - ], - }, - 'status': 'disabled', - } - ]) + result.append([ temp, ]) w = self.wpa2_personal() if w: result.append([w]) From 37b5194ce21ae1b32798449d40b4b4ddee444305 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 21 Jul 2017 10:58:32 +0200 Subject: [PATCH 165/342] [airos] fixed validation error in aaa test --- tests/airos/test_aaa.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index d9f3d2dc8..fc1014d05 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -58,7 +58,7 @@ def test_ap_psk_authentication(self): "radio": "ath0", "ssid": "i-like-pasta", "encryption": { - "proto": "wpa2_personal", + "protocol": "wpa2_personal", "key": "and-pizza-too", }, }, @@ -100,6 +100,7 @@ def test_sta_no_authentication(self): "mode": "station", "radio": "ath0", "ssid": "i-like-pasta", + "bssid": "00:11:22:33:44:55", }, }, ], @@ -129,8 +130,9 @@ def test_sta_psk_authentication(self): "mode": "station", "radio": "ath0", "ssid": "i-like-pasta", + "bssid": "00:11:22:33:44:55", "encryption": { - "proto": "wpa2_personal", + "protocol": "wpa2_personal", "key": "and-pizza-too", }, }, From 4792790c86d9233a4e4e4fdb4f1b2c5484887e9f Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 21 Jul 2017 12:52:33 +0200 Subject: [PATCH 166/342] [airos] added test for igmpproxy converter --- tests/airos/mock.py | 2 +- tests/airos/test_igmpproxy.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/airos/test_igmpproxy.py diff --git a/tests/airos/mock.py b/tests/airos/mock.py index 061cf57bc..fbead847c 100644 --- a/tests/airos/mock.py +++ b/tests/airos/mock.py @@ -101,7 +101,7 @@ class HttpdAirOs(AirOs): ] -class Igmpproxy(AirOs): +class IgmpproxyAirOs(AirOs): """ Mock backend with converter for igmpproxy """ diff --git a/tests/airos/test_igmpproxy.py b/tests/airos/test_igmpproxy.py new file mode 100644 index 000000000..a2e2933f8 --- /dev/null +++ b/tests/airos/test_igmpproxy.py @@ -0,0 +1,15 @@ +from .mock import ConverterTest, IgmpproxyAirOs + + +class Igmpproxyconverter(ConverterTest): + backend = IgmpproxyAirOs + + def test_ebtables(self): + o = self.backend({}) + o.to_intermediate() + expected = [ + { + 'status': 'disabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['igmpproxy'], expected) From ae6924469153f098ee3952237aa9d3926726efa0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 21 Jul 2017 12:59:51 +0200 Subject: [PATCH 167/342] [airos][test] renamed test case class --- tests/airos/test_aaa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index fc1014d05..fd5194fcf 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -1,7 +1,7 @@ from .mock import AaaAirOs, ConverterTest -class TestResolvConverter(ConverterTest): +class TestAaaConverter(ConverterTest): backend = AaaAirOs From 4ff38205ba0c12b8a7dce0b6ad853235235d591f Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 21 Jul 2017 13:01:16 +0200 Subject: [PATCH 168/342] [airos] added test in netconf converter for dhcp address --- tests/airos/test_netconf.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index d1291453d..61c06252a 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -420,6 +420,36 @@ def test_other(self): self.assertEqualConfig(o.intermediate_data['netconf'], expected) + def test_dhcp(self): + o = self.backend({ + 'interfaces': [ + { + 'name': 'eth0', + 'type': 'ethernet', + 'addresses': [ + { + 'proto': 'dhcp', + 'family': 'ipv4', + }, + ] + }, + ], + }) + o.to_intermediate() + expected = [ + { + '1.autoip.status': 'enabled', + '1.autoneg': 'enabled', + '1.devname': 'eth0', + '1.flowcontrol.rx.status': 'enabled', + '1.flowcontrol.tx.status': 'enabled', + '1.mtu': 1500, + '1.status': 'enabled', + '1.up': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['netconf'], expected) + def test_more_interfaces(self): o = self.backend({ 'interfaces': [ From f3303f633ea1527e1ba3164db91d5bd6ca3a111d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 21 Jul 2017 15:24:52 +0200 Subject: [PATCH 169/342] [airos] added test for ebtables converter --- tests/airos/test_ebtables.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/airos/test_ebtables.py diff --git a/tests/airos/test_ebtables.py b/tests/airos/test_ebtables.py new file mode 100644 index 000000000..f212b3b45 --- /dev/null +++ b/tests/airos/test_ebtables.py @@ -0,0 +1,17 @@ +from .mock import ConverterTest, EbtablesAirOs + + +class EbtablesConverter(ConverterTest): + backend = EbtablesAirOs + + def test_ebtables(self): + o = self.backend({}) + o.to_intermediate() + expected = [ + { + 'sys.fw.status': 'disabled', + 'sys.status': 'enabled', + 'status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) From 0786914b72885ba252469fb81edfebbba1f3215b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 11:11:35 +0200 Subject: [PATCH 170/342] [airos] added sorting to intermediate representation before rendering --- netjsonconfig/backends/airos/airos.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 71f901c0f..f8bcc44e0 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from ..base.backend import BaseBackend from .converters import (Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, Httpd, Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, @@ -9,6 +10,11 @@ from .schema import schema +def to_ordered_list(value): + flattened = flatten(intermediate_to_list(value)) + return [OrderedDict(sorted(x.items())) for x in flattened if x != {}] + + class AirOs(BaseBackend): """ AirOS backend @@ -53,4 +59,4 @@ class AirOs(BaseBackend): def to_intermediate(self): super(AirOs, self).to_intermediate() for k, v in self.intermediate_data.items(): - self.intermediate_data[k] = [x for x in flatten(intermediate_to_list(v)) if x != {}] + self.intermediate_data[k] = to_ordered_list(v) From bf8efd146ae5c937caab134fd08d1b82b933943b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 12:15:47 +0200 Subject: [PATCH 171/342] [airos] added stp status handler for bridge converter --- netjsonconfig/backends/airos/converters.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index bbe40eaf1..dc5d065ec 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -120,6 +120,12 @@ def to_intermediate(self): class Bridge(AirOsConverter): netjson_key = 'interfaces' + def stp_status(self, interface): + if interface.get('stp', False): + return 'enabled' + else: + return 'disabled' + def to_intermediate(self): result = [] original = [ @@ -138,9 +144,7 @@ def to_intermediate(self): 'devname': interface['name'], 'port': bridge_ports, 'status': status(interface), - 'stp': { - 'status': 'enabled', - } + 'stp': {'status': self.stp_status(interface)} }) result.append(bridges) From 952a60c2e977180d2902604c85024ad5f74ff62c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 12:38:13 +0200 Subject: [PATCH 172/342] [airos] added schema and handler for autoneg and flowcontrol property --- docs/source/backends/airos.rst | 21 ++++++++++++++ netjsonconfig/backends/airos/converters.py | 32 ++++++++++++++++------ netjsonconfig/backends/airos/schema.py | 10 +++++++ 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 7b262cc0c..3313a4b04 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -91,6 +91,27 @@ As an example here is a snippet that set the vlan ``eth0.2`` to be the managemen ] } +Ethernet +======== + +The ``ethernet`` interface can be configured to allow auto-negotiation and flow control with the properties ``autoneg`` and ``flowcontrol`` + +As an example here is a snippet that enables both auto-negotiation and flow control + +.. code-block:: json + + { + "interfaces": [ + { + "type": "ethernet", + "name": "eth0", + "autoneg": true, + "flowcontrol": true + } + ] + } + + DNS servers ----------- diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index dc5d065ec..b229e8ebd 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -268,6 +268,26 @@ def type_to_role(self, typestr): } return roles.get(typestr, '') + def autoneg_status(self, interface): + if interface.get('autoneg'): + return 'enabled' + else: + return 'disabled' + + def flowcontrol_status(self, interface): + if interface.get('flowcontrol'): + status = 'enabled' + else: + status = 'disabled' + return { + 'rx': { + 'status': status, + }, + 'tx': { + 'status': status, + }, + } + def to_intermediate(self): result = [] interfaces = [] @@ -282,15 +302,9 @@ def to_intermediate(self): } # handle interface type quirks if interface['type'] == 'ethernet' and '.' not in interface['name']: - base['autoneg'] = 'enabled' - base['flowcontrol'] = { - 'rx': { - 'status': 'enabled', - }, - 'tx': { - 'status': 'enabled', - }, - } + base['autoneg'] = self.autoneg_status(interface) + + base['flowcontrol'] = self.flowcontrol_status(interface) if interface['type'] == 'wireless': base['devname'] = interface['wireless']['radio'] diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 36506315a..9e103d526 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -60,6 +60,16 @@ }, }, }, + "interface_settings": { + "properties": { + "authoneg": { + "type": "boolean", + }, + "flowcontrol": { + "type": "boolean", + } + } + } }, "properties": { "netmode": { From 691a14bb53c4b10aa0f1d9775029784f031e5da0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 15:43:50 +0200 Subject: [PATCH 173/342] [airos] updated ntpclient converter to use ntp schema from openwrt --- netjsonconfig/backends/airos/converters.py | 37 ++++++++++++++-------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index b229e8ebd..055b0ce48 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -349,22 +349,33 @@ def to_intermediate(self): class Ntpclient(AirOsConverter): - netjson_key = 'ntp_servers' + netjson_key = 'ntp' + + default_servers = [ + "0.openwrt.pool.ntp.org", + "1.openwrt.pool.ntp.org", + "2.openwrt.pool.ntp.org", + "3.openwrt.pool.ntp.org", + ] + + def ntp_status(self, ntp): + if ntp.get('enabled', True): + return 'enabled' + else: + return 'disabled' def to_intermediate(self): result = [] - temp = [] - original = get_copy(self.netjson, self.netjson_key, []) - if original: - for ntp in original: - temp.append({ - 'server': ntp, - 'status': 'enabled', - }) - result.append(temp) - result.append({'status': 'enabled'}) - else: - result.append({'status': 'disabled'}) + servers = [] + original = get_copy(self.netjson, self.netjson_key, {}) + result.append({'status': self.ntp_status(original)}) + + for ntp in original.get('servers', self.default_servers): + servers.append({ + 'server': ntp, + 'status': 'enabled', + }) + result.append(servers) return (('ntpclient', result),) From 4e011742732b470da8e76fbf8c719c1208559067 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 16:22:15 +0200 Subject: [PATCH 174/342] [airos] added conversion to OrderedDict in converter test case --- tests/airos/mock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/airos/mock.py b/tests/airos/mock.py index fbead847c..59459db19 100644 --- a/tests/airos/mock.py +++ b/tests/airos/mock.py @@ -1,6 +1,7 @@ from unittest import TestCase from netjsonconfig import AirOs +from netjsonconfig.backends.airos.airos import to_ordered_list from netjsonconfig.backends.airos.converters import (Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, Httpd, Igmpproxy, @@ -34,7 +35,7 @@ def assertEqualConfig(self, a, b): If an element fails the assertion will be the only one printed """ - for (a, b) in zip(a, b): + for (a, b) in zip(a, to_ordered_list(b)): self.assertEqual(a, b) From efc4f7803e3a6b4be298e471ac6fb51b61224f0a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 16:24:02 +0200 Subject: [PATCH 175/342] [airos] updated test results in ntp converter test --- tests/airos/test_ntp.py | 71 +++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/tests/airos/test_ntp.py b/tests/airos/test_ntp.py index f66f4dbef..c8f5e53db 100644 --- a/tests/airos/test_ntp.py +++ b/tests/airos/test_ntp.py @@ -7,12 +7,28 @@ class TestResolvConverter(ConverterTest): def test_ntp_key(self): o = self.backend({ - "ntp_servers": [], + 'ntp_server': [], }) o.to_intermediate() expected = [ { - 'status': 'disabled', + 'status': 'enabled', + }, + { + '1.server': '0.openwrt.pool.ntp.org', + '1.status': 'enabled', + }, + { + '2.server': '1.openwrt.pool.ntp.org', + '2.status': 'enabled', + }, + { + '3.server': '2.openwrt.pool.ntp.org', + '3.status': 'enabled', + }, + { + '4.server': '3.openwrt.pool.ntp.org', + '4.status': 'enabled', }, ] @@ -20,31 +36,52 @@ def test_ntp_key(self): def test_no_ntp_server(self): o = self.backend({ - "ntp_servers": [], + 'ntp': { + 'enabled': False, + } }) o.to_intermediate() expected = [ { 'status': 'disabled', }, + { + '1.server': '0.openwrt.pool.ntp.org', + '1.status': 'enabled', + }, + { + '2.server': '1.openwrt.pool.ntp.org', + '2.status': 'enabled', + }, + { + '3.server': '2.openwrt.pool.ntp.org', + '3.status': 'enabled', + }, + { + '4.server': '3.openwrt.pool.ntp.org', + '4.status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) def test_single_ntp_server(self): o = self.backend({ - "ntp_servers": [ - '0.openwrt.pool.ntp.org', - ], + 'ntp': { + 'enabled': True, + 'server': [ + '0.openwrt.pool.ntp.org', + ] + }, }) o.to_intermediate() expected = [ { - '1.server': '0.openwrt.pool.ntp.org', - '1.status': 'enabled', + 'status': 'enabled', }, { - 'status': 'enabled', + '1.server': '0.openwrt.pool.ntp.org', + '1.status': 'enabled', }, ] @@ -52,13 +89,18 @@ def test_single_ntp_server(self): def test_multiple_ntp_server(self): o = self.backend({ - "ntp_servers": [ - '0.openwrt.pool.ntp.org', - '1.openwrt.pool.ntp.org', - ], + 'ntp': { + 'server': [ + '0.openwrt.pool.ntp.org', + '1.openwrt.pool.ntp.org', + ], + } }) o.to_intermediate() expected = [ + { + 'status': 'enabled', + }, { '1.server': '0.openwrt.pool.ntp.org', '1.status': 'enabled', @@ -67,9 +109,6 @@ def test_multiple_ntp_server(self): '2.server': '1.openwrt.pool.ntp.org', '2.status': 'enabled', }, - { - 'status': 'enabled', - }, ] self.assertEqualConfig(o.intermediate_data['ntpclient'], expected) From 8084ad2d776dc5e2f4a6be62e886fb2370dc82bf Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 16:24:51 +0200 Subject: [PATCH 176/342] [airos] fix invalid netjson in wpasupplicant converter --- tests/airos/test_wpasupplicant.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 00665a5bf..0d815d3dc 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -26,9 +26,7 @@ def test_invalid_encryption(self): "radio": "radio0", "mode": "station", "ssid": "ap-ssid-example", - }, - "encryption": { - "protocol": "wep", + "encryption": {"protocol": "wep"}, }, } ] @@ -89,10 +87,10 @@ def test_wpa2_personal(self): "mode": "station", "ssid": "ap-ssid-example", "bssid": "00:11:22:33:44:55", - }, - "encryption": { - "protocol": "wpa2_personal", - "key": "cucumber", + "encryption": { + "protocol": "wpa2_personal", + "key": "cucumber", + }, }, } ] @@ -138,10 +136,10 @@ def test_wpa2_enterprise(self): "radio": "radio0", "mode": "station", "ssid": "ap-ssid-example", - }, - "encryption": { - "protocol": "wpa2_enterprise", - "key": "cucumber", + "encryption": { + "protocol": "wpa2_enterprise", + "key": "cucumber", + }, }, } ] @@ -198,6 +196,7 @@ def test_no_encryption(self): "radio": "radio0", "mode": "access_point", "ssid": "ap-ssid-example", + "encryption": {"protocol": "none"}, }, }, ] @@ -236,10 +235,10 @@ def test_wpa2_personal(self): "radio": "radio0", "mode": "access_point", "ssid": "ap-ssid-example", - }, - "encryption": { - "protocol": "wpa2_personal", - "key": "cucumber", + "encryption": { + "protocol": "wpa2_personal", + "key": "cucumber", + }, }, } ] From 91140c4ec2e1bab20cc8b0b14038b963a0e10127 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 16:54:57 +0200 Subject: [PATCH 177/342] [airps] changed all test to comply with recent changes --- tests/airos/test_aaa.py | 2 -- tests/airos/test_bridge.py | 8 ++++---- tests/airos/test_netconf.py | 24 ++++++++++++------------ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index fd5194fcf..2a18faec6 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -41,7 +41,6 @@ def test_ap_no_authentication(self): '1.radius.acct.1.port': 1813, '1.radius.acct.1.status': 'disabled', '1.radius.auth.1.port': 1812, - '1.radius.auth.1.status': 'disabled', '1.status': 'disabled', }, ] @@ -114,7 +113,6 @@ def test_sta_no_authentication(self): '1.radius.acct.1.port': 1813, '1.radius.acct.1.status': 'disabled', '1.radius.auth.1.port': 1812, - '1.radius.auth.1.status': 'disabled', '1.status': 'disabled', }, ] diff --git a/tests/airos/test_bridge.py b/tests/airos/test_bridge.py index f0f1d0668..f232693bc 100644 --- a/tests/airos/test_bridge.py +++ b/tests/airos/test_bridge.py @@ -42,7 +42,7 @@ def test_active_bridge(self): '1.port.2.devname': 'eth1', '1.port.2.status': 'enabled', '1.status': 'enabled', - '1.stp.status': 'enabled' + '1.stp.status': 'disabled' }, { 'status': 'enabled', @@ -85,7 +85,7 @@ def test_disabled_bridge(self): '1.port.2.devname': 'eth1', '1.port.2.status': 'enabled', '1.status': 'disabled', - '1.stp.status': 'enabled' + '1.stp.status': 'disabled' }, { 'status': 'enabled', @@ -147,7 +147,7 @@ def test_many_bridges(self): '1.port.2.devname': 'eth1', '1.port.2.status': 'enabled', '1.status': 'disabled', - '1.stp.status': 'enabled' + '1.stp.status': 'disabled' }, { '2.comment': '', @@ -157,7 +157,7 @@ def test_many_bridges(self): '2.port.2.devname': 'eth3', '2.port.2.status': 'enabled', '2.status': 'enabled', - '2.stp.status': 'enabled' + '2.stp.status': 'disabled' }, { 'status': 'enabled', diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index 61c06252a..553969180 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -33,10 +33,10 @@ def test_active_interface(self): expected = [ { '1.autoip.status': 'disabled', - '1.autoneg': 'enabled', + '1.autoneg': 'disabled', '1.devname': 'eth0', - '1.flowcontrol.tx.status': 'enabled', - '1.flowcontrol.rx.status': 'enabled', + '1.flowcontrol.tx.status': 'disabled', + '1.flowcontrol.rx.status': 'disabled', '1.mtu': 1500, '1.status': 'enabled', '1.up': 'enabled', @@ -62,10 +62,10 @@ def test_inactive_interface(self): expected = [ { '1.autoip.status': 'disabled', - '1.autoneg': 'enabled', + '1.autoneg': 'disabled', '1.devname': 'eth0', - '1.flowcontrol.tx.status': 'enabled', - '1.flowcontrol.rx.status': 'enabled', + '1.flowcontrol.tx.status': 'disabled', + '1.flowcontrol.rx.status': 'disabled', '1.mtu': 1500, '1.status': 'enabled', '1.up': 'disabled', @@ -439,10 +439,10 @@ def test_dhcp(self): expected = [ { '1.autoip.status': 'enabled', - '1.autoneg': 'enabled', + '1.autoneg': 'disabled', '1.devname': 'eth0', - '1.flowcontrol.rx.status': 'enabled', - '1.flowcontrol.tx.status': 'enabled', + '1.flowcontrol.rx.status': 'disabled', + '1.flowcontrol.tx.status': 'disabled', '1.mtu': 1500, '1.status': 'enabled', '1.up': 'enabled', @@ -489,10 +489,10 @@ def test_more_interfaces(self): expected = [ { '1.autoip.status': 'disabled', - '1.autoneg': 'enabled', + '1.autoneg': 'disabled', '1.devname': 'eth0', - '1.flowcontrol.rx.status': 'enabled', - '1.flowcontrol.tx.status': 'enabled', + '1.flowcontrol.rx.status': 'disabled', + '1.flowcontrol.tx.status': 'disabled', '1.mtu': 1500, '1.status': 'enabled', '1.up': 'enabled', From ff282ac90d2a42422cf92ab5dc9db2ee8f219425 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 17:21:03 +0200 Subject: [PATCH 178/342] [airos] added schema for ntp servers with defaults, use them in converter --- netjsonconfig/backends/airos/converters.py | 9 +---- netjsonconfig/backends/airos/schema.py | 43 ++++++++++++++++++++++ tests/airos/test_ntp.py | 18 ++++----- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 055b0ce48..7638d996b 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -3,6 +3,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter +from .schema import default_ntp_servers from .wpasupplicant import available_mode_authentication @@ -351,12 +352,6 @@ def to_intermediate(self): class Ntpclient(AirOsConverter): netjson_key = 'ntp' - default_servers = [ - "0.openwrt.pool.ntp.org", - "1.openwrt.pool.ntp.org", - "2.openwrt.pool.ntp.org", - "3.openwrt.pool.ntp.org", - ] def ntp_status(self, ntp): if ntp.get('enabled', True): @@ -370,7 +365,7 @@ def to_intermediate(self): original = get_copy(self.netjson, self.netjson_key, {}) result.append({'status': self.ntp_status(original)}) - for ntp in original.get('servers', self.default_servers): + for ntp in original.get('server', default_ntp_servers): servers.append({ 'server': ntp, 'status': 'enabled', diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 9e103d526..57d1008c9 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -14,6 +14,13 @@ on a bridge """ +default_ntp_servers = [ + "0.pool.ntp.org", + "1.pool.ntp.org", + "2.pool.ntp.org", + "3.pool.ntp.org", +] + override_schema = { "type": "object", "addtionalProperties": True, @@ -96,6 +103,42 @@ "salt", ], }, + "ntp": { + "type": "object", + "title": "NTP Settings", + "additionalProperties": True, + "propertyOrder": 8, + "properties": { + "enabled": { + "type": "boolean", + "title": "enable NTP client", + "default": True, + "format": "checkbox", + "propertyOrder": 1, + }, + "enable_server": { + "type": "boolean", + "title": "enable NTP server", + "default": False, + "format": "checkbox", + "propertyOrder": 2, + }, + "server": { + "title": "NTP Servers", + "description": "NTP server candidates", + "type": "array", + "uniqueItems": True, + "additionalItems": True, + "propertyOrder": 3, + "items": { + "title": "NTP server", + "type": "string", + "format": "hostname" + }, + "default": default_ntp_servers, + } + } + }, }, } diff --git a/tests/airos/test_ntp.py b/tests/airos/test_ntp.py index c8f5e53db..6ae41a710 100644 --- a/tests/airos/test_ntp.py +++ b/tests/airos/test_ntp.py @@ -7,7 +7,7 @@ class TestResolvConverter(ConverterTest): def test_ntp_key(self): o = self.backend({ - 'ntp_server': [], + 'ntp': {} }) o.to_intermediate() expected = [ @@ -15,19 +15,19 @@ def test_ntp_key(self): 'status': 'enabled', }, { - '1.server': '0.openwrt.pool.ntp.org', + '1.server': '0.pool.ntp.org', '1.status': 'enabled', }, { - '2.server': '1.openwrt.pool.ntp.org', + '2.server': '1.pool.ntp.org', '2.status': 'enabled', }, { - '3.server': '2.openwrt.pool.ntp.org', + '3.server': '2.pool.ntp.org', '3.status': 'enabled', }, { - '4.server': '3.openwrt.pool.ntp.org', + '4.server': '3.pool.ntp.org', '4.status': 'enabled', }, ] @@ -46,19 +46,19 @@ def test_no_ntp_server(self): 'status': 'disabled', }, { - '1.server': '0.openwrt.pool.ntp.org', + '1.server': '0.pool.ntp.org', '1.status': 'enabled', }, { - '2.server': '1.openwrt.pool.ntp.org', + '2.server': '1.pool.ntp.org', '2.status': 'enabled', }, { - '3.server': '2.openwrt.pool.ntp.org', + '3.server': '2.pool.ntp.org', '3.status': 'enabled', }, { - '4.server': '3.openwrt.pool.ntp.org', + '4.server': '3.pool.ntp.org', '4.status': 'enabled', }, ] From b24ce49d50ff840777c86a70306e6dbb25d8d614 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 17:25:30 +0200 Subject: [PATCH 179/342] [airos] added bssid configuration to wireless section --- netjsonconfig/backends/airos/converters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 7638d996b..7af0c087b 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -637,6 +637,7 @@ def to_intermediate(self): 'signal_led4': 15, 'signal_led_status': 'enabled', 'ssid': w['wireless']['ssid'], + 'ap': w['wireless']['bssid'], 'status': status(w), 'wds': {'status': 'enabled'}, }) From ff4502047504eaaccde7c1d6feba9f0e056d9b17 Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Mon, 24 Jul 2017 18:09:24 +0200 Subject: [PATCH 180/342] [airos] Fixed schema issue described in #93 --- netjsonconfig/backends/airos/schema.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 57d1008c9..17f8bf5ff 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -142,9 +142,13 @@ }, } +schema['definitions']['encryption_wireless_property_ap'] = \ + override_schema['definitions']['encryption_wireless_property_ap'] schema = merge_config( default_schema, override_schema ) +schema['definitions']['encryption_wireless_property_sta'] = \ + override_schema['definitions']['encryption_wireless_property_sta'] __all__ = [schema] From 6dd82ee4cd5458c0c94b2f7ae2dcd48d8e68820e Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Mon, 24 Jul 2017 18:09:42 +0200 Subject: [PATCH 181/342] [airos] Reformatted schema indentation etc --- netjsonconfig/backends/airos/schema.py | 219 ++++++++++++------------- 1 file changed, 108 insertions(+), 111 deletions(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 17f8bf5ff..2230a0402 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -22,133 +22,130 @@ ] override_schema = { - "type": "object", - "addtionalProperties": True, - "definitions": { - "base_address": { - "properties": { - "management": { - "type": "boolean", - "default": False, - } + "type": "object", + "addtionalProperties": True, + "definitions": { + "base_address": { + "properties": { + "management": { + "type": "boolean", + "default": False, } - }, - "encryption_wireless_property_ap": { - "properties": { - "encryption": { - "type": "object", - "title": "Encryption", - "required": [ - "protocol", - ], - "propertyOrder": 20, - "oneOf": [ - {"$ref": "#/definitions/encryption_none"}, - {"$ref": "#/definitions/encryption_wpa_personal"}, - {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, - ], - }, + } + }, + "encryption_wireless_property_ap": { + "properties": { + "encryption": { + "type": "object", + "title": "Encryption", + "required": [ + "protocol", + ], + "propertyOrder": 20, + "oneOf": [ + {"$ref": "#/definitions/encryption_none"}, + {"$ref": "#/definitions/encryption_wpa_personal"}, + {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, + ], }, }, - "encryption_wireless_property_sta": { - "properties": { - "encryption": { - "type": "object", - "title": "Encryption", - "required": [ - "protocol", - ], - "propertyOrder": 20, - "oneOf": [ - {"$ref": "#/definitions/encryption_none"}, - {"$ref": "#/definitions/encryption_wpa_personal"}, - {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, - ], - }, + }, + "encryption_wireless_property_sta": { + "properties": { + "encryption": { + "type": "object", + "title": "Encryption", + "required": [ + "protocol", + ], + "propertyOrder": 20, + "oneOf": [ + {"$ref": "#/definitions/encryption_none"}, + {"$ref": "#/definitions/encryption_wpa_personal"}, + {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, + ], }, }, - "interface_settings": { - "properties": { - "authoneg": { - "type": "boolean", - }, - "flowcontrol": { - "type": "boolean", - } + }, + "interface_settings": { + "properties": { + "authoneg": { + "type": "boolean", + }, + "flowcontrol": { + "type": "boolean", } } + } + }, + "properties": { + "netmode": { + "enum": [ + "bridge", + "router", + ], + "default": "bridge", + "type": "string", }, - "properties": { - "netmode": { - "enum": [ - "bridge", - "router", - ], - "default": "bridge", - "type": "string", - }, - "user": { - "additionalProperties": True, - "properties": { - "name": { - "type": "string", - }, - "salt": { - "type": "string", - }, + "user": { + "additionalProperties": True, + "properties": { + "name": { + "type": "string", + }, + "salt": { + "type": "string", }, - "required": [ - "name", - "password", - "salt", - ], }, - "ntp": { - "type": "object", - "title": "NTP Settings", - "additionalProperties": True, - "propertyOrder": 8, - "properties": { - "enabled": { - "type": "boolean", - "title": "enable NTP client", - "default": True, - "format": "checkbox", - "propertyOrder": 1, - }, - "enable_server": { - "type": "boolean", - "title": "enable NTP server", - "default": False, - "format": "checkbox", - "propertyOrder": 2, + "required": [ + "name", + "password", + "salt", + ], + }, + "ntp": { + "type": "object", + "title": "NTP Settings", + "additionalProperties": True, + "propertyOrder": 8, + "properties": { + "enabled": { + "type": "boolean", + "title": "enable NTP client", + "default": True, + "format": "checkbox", + "propertyOrder": 1, + }, + "enable_server": { + "type": "boolean", + "title": "enable NTP server", + "default": False, + "format": "checkbox", + "propertyOrder": 2, + }, + "server": { + "title": "NTP Servers", + "description": "NTP server candidates", + "type": "array", + "uniqueItems": True, + "additionalItems": True, + "propertyOrder": 3, + "items": { + "title": "NTP server", + "type": "string", + "format": "hostname" }, - "server": { - "title": "NTP Servers", - "description": "NTP server candidates", - "type": "array", - "uniqueItems": True, - "additionalItems": True, - "propertyOrder": 3, - "items": { - "title": "NTP server", - "type": "string", - "format": "hostname" - }, - "default": default_ntp_servers, - } + "default": default_ntp_servers, } - }, + } }, - } + }, +} + +schema = merge_config(default_schema, override_schema) schema['definitions']['encryption_wireless_property_ap'] = \ override_schema['definitions']['encryption_wireless_property_ap'] -schema = merge_config( - default_schema, - override_schema - ) schema['definitions']['encryption_wireless_property_sta'] = \ override_schema['definitions']['encryption_wireless_property_sta'] -__all__ = [schema] From efee53221f51ed4b7d3475b6c532e0ed42c9bbea Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 18:40:49 +0200 Subject: [PATCH 182/342] [airos] added default routes handling to routes converter --- netjsonconfig/backends/airos/converters.py | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 7af0c087b..0b6b8a5b0 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -440,10 +440,35 @@ def to_intermediate(self): class Route(AirOsConverter): netjson_key = 'routes' + def default_routes(self): + def is_default_route(interface): + try: + t = [addr.get('gateway', '') for addr in interface['addresses']] + return any(t) + except KeyError: + return False + + result = [] + original = [x for x in get_copy(self.netjson, 'interfaces', []) if is_default_route(x)] + for interface in original: + for address in interface['addresses']: + try: + result.append({ + 'devname': interface['name'], + 'gateway': address['gateway'], + 'ip': '0.0.0.0', + 'netmask': 0, + 'status': 'enabled', + }) + except KeyError: + pass + return result + def to_intermediate(self): result = [] - original = get_copy(self.netjson, self.netjson_key, []) routes = [] + routes = self.default_routes() + original = get_copy(self.netjson, self.netjson_key, []) for r in original: network = ip_interface(r['destination']) temp = {} From 737e63680647d6ce339a475f8dc3d8a48b70a2ff Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 18:44:17 +0200 Subject: [PATCH 183/342] [airos] reorder schema properties --- netjsonconfig/backends/airos/schema.py | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 2230a0402..7a301433a 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -87,22 +87,6 @@ "default": "bridge", "type": "string", }, - "user": { - "additionalProperties": True, - "properties": { - "name": { - "type": "string", - }, - "salt": { - "type": "string", - }, - }, - "required": [ - "name", - "password", - "salt", - ], - }, "ntp": { "type": "object", "title": "NTP Settings", @@ -139,6 +123,22 @@ } } }, + "user": { + "additionalProperties": True, + "properties": { + "name": { + "type": "string", + }, + "salt": { + "type": "string", + }, + }, + "required": [ + "name", + "password", + "salt", + ], + }, }, } From 3af88fed9b9ca8e3f30a69563cc1f024319535ef Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 24 Jul 2017 18:57:45 +0200 Subject: [PATCH 184/342] [airos] added draft for sshd property schema and sshd converter --- netjsonconfig/backends/airos/converters.py | 15 ++++++--- netjsonconfig/backends/airos/schema.py | 39 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 0b6b8a5b0..7a6380fe0 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -501,14 +501,21 @@ def to_intermediate(self): class Sshd(AirOsConverter): - netjson_key = 'general' + netjson_key = 'sshd' def to_intermediate(self): + def status(original, key='enabled'): + if original.get(key, True): + return 'enabled' + else: + return 'disabled' + result = [] + original = get_copy(self.netjson, self.netjson_key) result.append({ - 'auth': {'passwd': 'enabled'}, - 'port': 22, - 'status': 'enabled', + 'auth': {'passwd': status(original, 'password_auth')}, + 'port': original.get('port', 22), + 'status': status(original, 'enabled'), }) return (('sshd', result),) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 7a301433a..9230981f1 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -123,6 +123,45 @@ } } }, + "sshd": { + "type": "object", + "title": "SSHd settings", + "additionalProperties": True, + "properties": { + "port": { + "type": "integer", + "default": 22, + }, + "enabled": { + "type": "boolean", + "default": True, + }, + "password_auth": { + "type": "boolean", + "default": True, + }, + "keys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + }, + "key": { + "type": "string", + }, + "comment": { + "type": "string", + }, + "enabled": { + "type": "boolean", + }, + } + } + }, + }, + }, "user": { "additionalProperties": True, "properties": { From 29aed15d0d64800d51bc21e4ea60c2bbb3c1811c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 10:18:19 +0200 Subject: [PATCH 185/342] [airos] specify both location and mantainer from netjson --- docs/source/backends/airos.rst | 14 ++++++++++++++ netjsonconfig/backends/airos/converters.py | 11 ++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 3313a4b04..0edc1f194 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -56,6 +56,20 @@ JSON method General settings ---------------- +From the ``general`` key we can configure the contact and the location for a device using the ``contact`` and ``location`` properties. + +The following snippet specify both contact and location: + +.. code-block:: json + + { + "type": "DeviceConfiguration", + "general": { + "contact": "user@example.com", + "location": "Up in the roof" + } + } + Network interface ----------------- diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 7a6380fe0..3e7a44745 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -489,12 +489,13 @@ class Snmp(AirOsConverter): netjson_key = 'general' def to_intermediate(self): + original = get_copy(self.netson, self.netjson_key) result = [ - { - 'community': 'public', - 'contact': '', - 'location': '', - 'status': 'enabled', + { + 'community': 'public', + 'contact': original.get('mantainer', ''), + 'location': original.get('location', ''), + 'status': 'enabled', }, ] return (('snmp', result),) From 2b04c347429286218fd769a767bb30a6d475dbc8 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 10:18:47 +0200 Subject: [PATCH 186/342] [airos] make assignment explicit --- netjsonconfig/backends/airos/converters.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 3e7a44745..5e838c192 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -320,8 +320,7 @@ def to_intermediate(self): temp['role'] = self.type_to_role(interface['type']) # handle explicit address policy if addr['proto'] == 'dhcp': - temp['autoip'] = {} - temp['autoip']['status'] = 'enabled' + temp['autoip'] = {'status': 'enabled'} else: ip_and_mask = '%s/%d' % (addr['address'], addr['mask']) network = ip_interface(ip_and_mask) From 465ed7869c450fa53a3ea8555fbd2a21f4244a3e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 10:20:14 +0200 Subject: [PATCH 187/342] [airos] fix indentation in renderer cleanup --- netjsonconfig/backends/airos/renderers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/renderers.py b/netjsonconfig/backends/airos/renderers.py index aaa9cfd0f..7512c7523 100644 --- a/netjsonconfig/backends/airos/renderers.py +++ b/netjsonconfig/backends/airos/renderers.py @@ -5,6 +5,6 @@ class AirOsRenderer(BaseRenderer): def cleanup(self, output): stripped = [ - a.strip() for a in output.splitlines() if a.strip() - ] + a.strip() for a in output.splitlines() if a.strip() + ] return '\n'.join(stripped) From 671885a7e2b49d6070e8f3c06edc7aff083cb687 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 10:31:48 +0200 Subject: [PATCH 188/342] [airos] updated docs to use correct airos backend name --- docs/source/backends/airos.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 0edc1f194..0a16b0a76 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -4,20 +4,20 @@ AirOS Backend .. include:: ../_github.rst -The ``AirOS`` backend allows to generate AirOS v8.3 compatible configurations. +The ``AirOs`` backend allows to generate AirOS v8.3 compatible configurations. Initialization -------------- -.. automethod:: netjsonconfig.AirOS.__init__ +.. automethod:: netjsonconfig.AirOs.__init__ Initialization example: .. code-block:: python - from netjsonconfig import AirOS + from netjsonconfig import AirOs - router = AirOS({ + router = AirOs({ "general": { "hostname": "MasterAntenna" } @@ -33,24 +33,24 @@ read about the following basic concepts: Render method ------------- -.. automethod:: netjsonconfig.AirOS.render +.. automethod:: netjsonconfig.AirOs.render Generate method --------------- -.. automethod:: netjsonconfig.AirOS.generate +.. automethod:: netjsonconfig.AirOs.generate Write method ------------ -.. automethod:: netjsonconfig.AirOS.write +.. automethod:: netjsonconfig.AirOs.write JSON method ----------- -.. automethod:: netjsonconfig.AirOS.json +.. automethod:: netjsonconfig.AirOs.json General settings From 5a47172971f9d541e9323bde2db7089985a15fd2 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 10:32:50 +0200 Subject: [PATCH 189/342] [airos] updated docs for airos backend and intermediate representation [airos] fixed problem with ellipsis not recognised in json example [airos] updated docs for ntp client and ntp servers [airos] fixed netjoson examples in documentation to be compliant to netjson [airos] fixed typo [airos] added terminal dot [airos] added snippet for the undecided about authentication encryption --- docs/source/backends/airos.rst | 67 +++++++++++++++++++++------ docs/source/backends/intermediate.rst | 57 +++++++++-------------- 2 files changed, 75 insertions(+), 49 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 0a16b0a76..3325cd29a 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -149,11 +149,12 @@ The default values for this key are as reported below Netmode ------- -AirOS v8.3 can operate in ``bridge`` and ``router`` mode (but defaults to ``bridge``) and this can be specified with the ``netmode`` property +AirOS v8.3 can operate in ``bridge`` and ``router`` mode (but defaults to ``bridge``) and this can be specified with the ``netmode`` property. .. code-block:: json { + "type": "DeviceConfiguration", "netmode": "bridge" } @@ -162,16 +163,35 @@ NTP servers This is an extension to the `NetJSON` specification. -By setting the key ``ntp_servers`` in your input you can provide a list of ntp servers to use. +By setting the key ``ntp`` property in your input you can provide the configuration for the ntp client running on the device. .. code-block:: json { "type": "DeviceConfiguration", - ... - "ntp_servers": [ - "0.ubnt.pool.ntp.org" - ] + "ntp": { + "enabled": true, + "server": [ + "0.ubnt.pool.ntp.org" + ] + } + } + +For the lazy one we provide these defaults + +.. code-block:: json + + { + "type": "DeviceConfiguration", + "ntp": { + "enabled": true, + "server": [ + "0.pool.ntp.org", + "1.pool.ntp.org", + "2.pool.ntp.org", + "3.pool.ntp.org" + ] + } } Users @@ -188,7 +208,7 @@ From the antenna configuration take the user section. users.1.name=ubnt users.1.password=$1$yRo1tmtC$EcdoRX.JnD4VaEYgghgWg1 -I the line ``users.1.password=$1$yRo1tmtC$EcdoRX.JnD4VaEYgghgWg1`` there are both the salt and the password hash in the format ``$ algorithm $ salt $ hash $``, e.g in the previous block ``algorithm=1``, ``salt=yRo1tmtC`` and ``hash=EcdoRX.JnD4VaEYgghgWg1``. +In the line ``users.1.password=$1$yRo1tmtC$EcdoRX.JnD4VaEYgghgWg1`` there are both the salt and the password hash in the format ``$ algorithm $ salt $ hash $``, e.g in the previous block ``algorithm=1``, ``salt=yRo1tmtC`` and ``hash=EcdoRX.JnD4VaEYgghgWg1``. To specify the password in NetJSON use the ``user`` property. @@ -220,9 +240,11 @@ As an example here is a snippet that set the authentication protocol to WPA2 per { "name": "wlan0", "type": "wireless", - "encryption": { - "protocol": "wpa2_personal", - "key": "changeme" + "wireless": { + "encryption": { + "protocol": "wpa2_personal", + "key": "changeme" + } } } ] @@ -237,12 +259,31 @@ And another that set the authentication protocol to WPA2 enterprise, but this is { "name": "wlan0", "type": "wireless", - "encryption": { - "protocol": "wpa2_enterprise", - "key": "changeme" + "wireless": { + "encryption": { + "protocol": "wpa2_enterprise", + "key": "changeme" + } } } ] } +The ``encryption`` property **must** be specified otherwise you will experience a ``ValidationError``, if you are not sure on what you want +use this snippet to set to no encryption + +.. code-block:: json + + { + "interfaces": [ + { + "name": "wlan0", + "type": "wireless", + "wireless": { + "encryption": { + "protocol": "none" + } + } + } + } Leaving the `NetJSON Encryption object ` empty defaults to no encryption at all. diff --git a/docs/source/backends/intermediate.rst b/docs/source/backends/intermediate.rst index 142034826..35d262120 100644 --- a/docs/source/backends/intermediate.rst +++ b/docs/source/backends/intermediate.rst @@ -80,10 +80,11 @@ e.g. `vlan.1.devname=eth0` we store a list of dictionaries. { 'ipython' : True, } - ], - }, + ], + }, } - ]) + ] + ) And the resulting tree is: @@ -127,26 +128,10 @@ Flattening To avoid at all cost a recursive logic in the template we flatten the intermediate representation to something that has a *namespace* a *key* and a *value*. -This input NetJSON will be converted to a python :ref:`configuration_dictionary`: +The objective is to go from a python :ref:`configuration_dictionary` that we get from loading a NetJSON to the AirOS configuration. -.. code-block:: json +An input :ref:`configuration_dictionary` is just a python dictionary, e.g.: - //netjson - { - "type" : "DeviceConfiguration", - "interfaces" : [ - { - "name" : "eth0.1", - "type" : "ethernet", - "comment" : "management vlan" - }, - { - "name" : "eth0.2", - "type" : "ethernet", - "comment" : "traffic vlan" - } - ] - } .. code-block:: python @@ -157,11 +142,12 @@ This input NetJSON will be converted to a python :ref:`configuration_dictionary` 'name' : 'eth0.1', 'type' : 'ethernet', 'comment' : 'management vlan' + 'comment' : 'management' }, { 'name' : 'eth0.2', 'type' : 'ethernet', - 'comment' : 'traffic vlan' + 'comment' : 'traffic' } ] } @@ -191,21 +177,20 @@ resembles the target text, the output configuration. 'vlan', #options [ - { - # key : value - '1.devname' : 'eth0', - '1.id' : '1' - '1.status' : 'enabled', - '1.comment' : 'management' - }, - { - '2.devname' : 'eth0', - '2.id' : '2' - '2.status' : 'enabled', - '2.comment' : 'traffic' - } + { + # key : value + '1.devname' : 'eth0', + '1.id' : '1' + '1.status' : 'enabled', + '1.comment' : 'management' + }, + { + '2.devname' : 'eth0', + '2.id' : '2' + '2.status' : 'enabled', + '2.comment' : 'traffic' + } ] - ) And to do that we get rid of the multiple indentation levels by flattening the tree structure. From 98cc9579aa90678e358d8b86a49084e4d5a83431 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 11:35:06 +0200 Subject: [PATCH 190/342] [airos] updated schema with meta infor and defaults --- netjsonconfig/backends/airos/schema.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 9230981f1..2f7e15de5 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -69,11 +69,17 @@ }, "interface_settings": { "properties": { - "authoneg": { + "autoneg": { "type": "boolean", + "default": False, + "title": "Auto negotiation", + "description": "Enable autonegotiation on interface", }, "flowcontrol": { "type": "boolean", + "default": False, + "title": "Flow control", + "description": "Enable flow control on interface", } } } @@ -156,6 +162,7 @@ }, "enabled": { "type": "boolean", + "default": True, }, } } From 51b10351990436632cf6a68d27c4b6c5cda13ce9 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 11:40:47 +0200 Subject: [PATCH 191/342] [airos] check for index error on accessing a wireless interface --- netjsonconfig/backends/airos/converters.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 5e838c192..0faa70d74 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -68,10 +68,13 @@ def status(self): The aaa.status value is enabled when the interface is in access_point mode with wpa2_personal authentication """ - t = self.wireless()[0]['wireless'] - if t['mode'] == 'access_point' and t['encryption']['protocol'] == 'wpa2_personal': - return 'enabled' - else: + try: + t = self.wireless()[0]['wireless'] + if t['mode'] == 'access_point' and t['encryption']['protocol'] == 'wpa2_personal': + return 'enabled' + else: + return 'disabled' + except IndexError: return 'disabled' def ap_psk(self): From f1e83893adc4ce2551bbf58aa35ce267ed42c763 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 16:31:40 +0200 Subject: [PATCH 192/342] [airos] fixed typo in converter attribute --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 0faa70d74..2f1a9f623 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -491,7 +491,7 @@ class Snmp(AirOsConverter): netjson_key = 'general' def to_intermediate(self): - original = get_copy(self.netson, self.netjson_key) + original = get_copy(self.netjson, self.netjson_key) result = [ { 'community': 'public', From 712ebcceac36b2cc241da19b367617cadf523464 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 16:33:46 +0200 Subject: [PATCH 193/342] [airos] set default for sshd properties in sshd converter --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 2f1a9f623..f93acfcd2 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -514,7 +514,7 @@ def status(original, key='enabled'): return 'disabled' result = [] - original = get_copy(self.netjson, self.netjson_key) + original = get_copy(self.netjson, self.netjson_key, {}) result.append({ 'auth': {'passwd': status(original, 'password_auth')}, 'port': original.get('port', 22), From 540076c8ecd6f86a96d7fa3641a95349b8a37a40 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 25 Jul 2017 16:38:02 +0200 Subject: [PATCH 194/342] [airos] updated schema for user section with title Please enter the commit message for your changes. Lines starting --- netjsonconfig/backends/airos/schema.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 2f7e15de5..9199a170b 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -173,9 +173,15 @@ "additionalProperties": True, "properties": { "name": { + "title": "User name", + "type": "string", + }, + "password": { + "title": "Hashed password for user", "type": "string", }, "salt": { + "title": "Salt for hashing algorithm", "type": "string", }, }, From 6b42888b119da3ee97d93eab39ee0348ba626710 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 12:08:06 +0200 Subject: [PATCH 195/342] [airos] populated schema with titles and format --- netjsonconfig/backends/airos/schema.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 9199a170b..92fc5b183 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -92,6 +92,7 @@ ], "default": "bridge", "type": "string", + "title": "Network mode for device", }, "ntp": { "type": "object", @@ -137,14 +138,19 @@ "port": { "type": "integer", "default": 22, + "title": "Port for sshd to listen on", }, "enabled": { "type": "boolean", "default": True, + "title": "Enable ssh server", + "format": "checkbox", }, "password_auth": { "type": "boolean", "default": True, + "title": "Enable password authentication", + "format": "checkbox", }, "keys": { "type": "array", @@ -153,16 +159,22 @@ "properties": { "type": { "type": "string", + "title": "Key algorithm", }, "key": { "type": "string", + "title": "Key file content", }, "comment": { "type": "string", + "default": "", + "title": "comment", }, "enabled": { "type": "boolean", "default": True, + "title": "Enable key", + "format": "checkbox", }, } } From 1750a0ddd1fd9aaed4b4bd14312cc084c25406e7 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 12:24:58 +0200 Subject: [PATCH 196/342] [airos] schema propertyOrder enchancement --- netjsonconfig/backends/airos/schema.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 92fc5b183..ab86e05e4 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -30,6 +30,10 @@ "management": { "type": "boolean", "default": False, + "title": "Management", + "description": "Management interface", + "format": "checkbox", + "propertyOrder": 0, } } }, @@ -74,12 +78,14 @@ "default": False, "title": "Auto negotiation", "description": "Enable autonegotiation on interface", + "propertyOrder": 0, }, "flowcontrol": { "type": "boolean", "default": False, "title": "Flow control", "description": "Enable flow control on interface", + "propertyOrder": 1, } } } @@ -138,22 +144,29 @@ "port": { "type": "integer", "default": 22, - "title": "Port for sshd to listen on", + "title": "Port", + "description": "Port for sshd to listen on", + "propertyOrder": 0, }, "enabled": { "type": "boolean", "default": True, "title": "Enable ssh server", "format": "checkbox", + "propertyOrder": 1, }, "password_auth": { "type": "boolean", "default": True, "title": "Enable password authentication", "format": "checkbox", + "propertyOrder": 2, }, "keys": { "type": "array", + "propertyOrder": 3, + "title": "Keys", + "description": "User keys", "items": { "type": "object", "properties": { @@ -168,7 +181,7 @@ "comment": { "type": "string", "default": "", - "title": "comment", + "title": "Comment", }, "enabled": { "type": "boolean", From f65baffb375fe9c7bb2eae3322321f8e9cd3b18f Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 12:34:59 +0200 Subject: [PATCH 197/342] [airos] move flowcontrol property up in the list --- netjsonconfig/backends/airos/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index ab86e05e4..04b572af2 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -85,7 +85,7 @@ "default": False, "title": "Flow control", "description": "Enable flow control on interface", - "propertyOrder": 1, + "propertyOrder": 0, } } } From d3ad3cd47a3287c5812d68ea161b994229f48e6e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 12:35:21 +0200 Subject: [PATCH 198/342] [airos] added constrain on wireless interface modes --- netjsonconfig/backends/airos/schema.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 04b572af2..29cf8fc19 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -88,6 +88,28 @@ "propertyOrder": 0, } } + }, + "wireless_interface": { + "allOf": [ + { + "properties": { + "type": { + "type": "string", + "enum": ["wireless"], + "default": "wireless", + "propertyOrder": 1, + }, + "wireless": { + "type": "object", + "propertyOrder": 10, + "oneOf": [ + {"$ref": "#/definitions/ap_wireless_settings"}, + {"$ref": "#/definitions/sta_wireless_settings"}, + ] + } + } + } + ] } }, "properties": { From 4d71f2ba5a77f8b5a197395eb237f5ecba57d398 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 12:41:44 +0200 Subject: [PATCH 199/342] [airos] catch both IndexError and KeyError --- netjsonconfig/backends/airos/converters.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index f93acfcd2..a415b3301 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -70,11 +70,12 @@ def status(self): """ try: t = self.wireless()[0]['wireless'] - if t['mode'] == 'access_point' and t['encryption']['protocol'] == 'wpa2_personal': + if t['mode'] == 'access_point' and t['encryption']['protocol'] == 'wpa2_personal': return 'enabled' else: return 'disabled' - except IndexError: + except: + # catch both KeyError and IndexError return 'disabled' def ap_psk(self): From 63cb2874de11780eafad878c8ca3cbcbb8b33ae4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 12:42:07 +0200 Subject: [PATCH 200/342] [airos] added default for general keyword in snmp converter --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index a415b3301..da5d3bfd8 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -492,7 +492,7 @@ class Snmp(AirOsConverter): netjson_key = 'general' def to_intermediate(self): - original = get_copy(self.netjson, self.netjson_key) + original = get_copy(self.netjson, self.netjson_key, {}) result = [ { 'community': 'public', From 26b070998b3a8c246c5b0d569fab60487a8d8d12 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 12:42:47 +0200 Subject: [PATCH 201/342] [airos] added default for bssid getter in wireless converter --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index da5d3bfd8..6cd52cdf2 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -673,7 +673,7 @@ def to_intermediate(self): 'signal_led4': 15, 'signal_led_status': 'enabled', 'ssid': w['wireless']['ssid'], - 'ap': w['wireless']['bssid'], + 'ap': w['wireless'].get('bssid',''), 'status': status(w), 'wds': {'status': 'enabled'}, }) From 83c8a49c6d1b27659aa27fd1244ad5932e1d811e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 12:51:40 +0200 Subject: [PATCH 202/342] Revert "[airos] added constrain on wireless interface modes" This reverts commit a6e57e44b25ae41c75ab9c482231ddc066206b26. --- netjsonconfig/backends/airos/schema.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 29cf8fc19..04b572af2 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -88,28 +88,6 @@ "propertyOrder": 0, } } - }, - "wireless_interface": { - "allOf": [ - { - "properties": { - "type": { - "type": "string", - "enum": ["wireless"], - "default": "wireless", - "propertyOrder": 1, - }, - "wireless": { - "type": "object", - "propertyOrder": 10, - "oneOf": [ - {"$ref": "#/definitions/ap_wireless_settings"}, - {"$ref": "#/definitions/sta_wireless_settings"}, - ] - } - } - } - ] } }, "properties": { From e77a9ebae70a6ec1a4564dfc041100cab3041414 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 14:22:29 +0200 Subject: [PATCH 203/342] [airos] added schema, converter for gui property --- netjsonconfig/backends/airos/converters.py | 5 +++-- netjsonconfig/backends/airos/schema.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 6cd52cdf2..9dd4cc778 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -204,14 +204,15 @@ class Gui(AirOsConverter): netjson_key = 'gui' def to_intermediate(self): + original = get_copy(self.netjson, self.netjson_key, {}) result = [ { - 'language': 'en_US', + 'language': original.get('language', 'en_US'), }, { 'network': { 'advanced': { - 'status': 'enabled' + 'status': 'enabled', } } } diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 04b572af2..93cf92f8c 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -91,6 +91,17 @@ } }, "properties": { + "gui": { + "type": "object", + "properties": { + "language": { + "type": "string", + "default": "en_US", + "title": "Language", + "description": "Web interface language" + } + } + }, "netmode": { "enum": [ "bridge", From d79f501232650bcd10b452129d2b7056b8ce584d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 14:30:11 +0200 Subject: [PATCH 204/342] [airos] updated documentation for gui property --- docs/source/backends/airos.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 3325cd29a..09add5227 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -133,16 +133,16 @@ DNS servers GUI --- -As an extension to `NetJSON ` you can use the ``gui`` key to set the language of the interface and show the advanced network configuration option. +As an extension to `NetJSON ` you can use the ``gui`` key to set the language of the interface The default values for this key are as reported below .. code-block:: json { + "type": "DeviceConfiguration", "gui": { "language": "en_US", - "advanced": true } } @@ -286,4 +286,3 @@ use this snippet to set to no encryption } } } -Leaving the `NetJSON Encryption object ` empty defaults to no encryption at all. From 7ce8cef71a25fdba6316ea33a093ff7010d60567 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 15:00:13 +0200 Subject: [PATCH 205/342] [airos] set required properties for ssh key in netjson --- netjsonconfig/backends/airos/schema.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 93cf92f8c..627022b0b 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -180,6 +180,10 @@ "description": "User keys", "items": { "type": "object", + "required": [ + "type", + "key", + ], "properties": { "type": { "type": "string", From 8b06f578ad566d7fa119a549e55dc6dcf4a007fa Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 26 Jul 2017 15:01:30 +0200 Subject: [PATCH 206/342] [airos] added default for key type to schema --- netjsonconfig/backends/airos/schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 627022b0b..b72ad24e6 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -188,6 +188,7 @@ "type": { "type": "string", "title": "Key algorithm", + "default": "ssh-rsa", }, "key": { "type": "string", From a2af7fbadd8af3fc99e4c92f3f36f8c85f53d48d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 27 Jul 2017 10:20:12 +0200 Subject: [PATCH 207/342] [airos] added ssh-key handling in converter --- netjsonconfig/backends/airos/converters.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 9dd4cc778..5b2dda240 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -515,10 +515,26 @@ def status(original, key='enabled'): else: return 'disabled' + def to_key(x): + result = [] + for y in x: + result.append({ + 'status': status(y), + 'type': y['type'], + 'value': y['key'], + 'comment': y.get('comment', '') + }) + return result + result = [] original = get_copy(self.netjson, self.netjson_key, {}) + auth = {'passwd': status(original, 'password_auth')} + key = to_key(original.get('keys', [])) + if key: + auth.update({'key': key}) + result.append({ - 'auth': {'passwd': status(original, 'password_auth')}, + 'auth': auth, 'port': original.get('port', 22), 'status': status(original, 'enabled'), }) From 6b7f9ffb316800fcf5bbb34a65005d89a583716a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 27 Jul 2017 11:07:32 +0200 Subject: [PATCH 208/342] [airos] added required properties to sshd schema --- netjsonconfig/backends/airos/schema.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index b72ad24e6..4419f920b 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -151,6 +151,11 @@ "type": "object", "title": "SSHd settings", "additionalProperties": True, + "required": [ + "port", + "enabled", + "password_auth", + ], "properties": { "port": { "type": "integer", From 3302c17f4de8f97b7fbcf616e8781552bfbe62d4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 27 Jul 2017 11:09:05 +0200 Subject: [PATCH 209/342] [airos] added test for language property in gui property --- tests/airos/test_gui.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/airos/test_gui.py b/tests/airos/test_gui.py index b85e8114a..460b82a9d 100644 --- a/tests/airos/test_gui.py +++ b/tests/airos/test_gui.py @@ -20,3 +20,21 @@ def test_gui_key(self): ] self.assertEqualConfig(o.intermediate_data['gui'], expected) + + def test_language(self): + o = self.backend({ + 'gui': { + 'language' : 'it_IT', + }, + }) + o.to_intermediate() + expected = [ + { + 'language': 'it_IT', + }, + { + 'network.advanced.status': 'enabled', + }, + ] + + self.assertEqualConfig(o.intermediate_data['gui'], expected) From 75d10aae26b732202524c957e429ade7501d16c3 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 27 Jul 2017 13:21:16 +0200 Subject: [PATCH 210/342] [airos] fixed exnryption key nesting in interface --- netjsonconfig/backends/airos/converters.py | 8 ++++---- netjsonconfig/backends/airos/wpasupplicant.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 5b2dda240..76aef74c4 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -17,7 +17,7 @@ def status(config, key='disabled'): def get_psk(interface): t = { 'wpa': { - 'psk': interface['encryption']['key'], + 'psk': interface['wireless']['encryption']['key'], }, } return t @@ -29,7 +29,7 @@ def is_wpa2_personal(interface): authentication """ try: - return interface['encryption']['protocol'] == 'wpa2_personal' + return interface['wireless']['encryption']['protocol'] == 'wpa2_personal' except: return False @@ -717,8 +717,8 @@ def _station_intermediate(self, original): head = original[0] temp_dev['devname'] = head['wireless']['radio'] - if 'encryption' in head: - network = station_auth_protocols.get(head['encryption']['protocol'])(head) + if 'encryption' in head['wireless']: + network = station_auth_protocols.get(head['wireless']['encryption']['protocol'])(head) else: network = station_auth_protocols['none'](head) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index 828ba3762..4e738bdab 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -21,7 +21,7 @@ def ap_wpa2_personal(interface): in ``access_point`` mode """ return { - 'psk': interface['encryption']['key'], + 'psk': interface['wireless']['encryption']['key'], 'ssid': interface['wireless']['ssid'], 'key_mgmt': [ { @@ -57,7 +57,7 @@ def sta_wpa2_personal(interface): """ return { 'ssid': interface['wireless']['ssid'], - 'psk': interface['encryption']['key'], + 'psk': interface['wireless']['encryption']['key'], # no advanced authentication methods # with psk 'eap': [ From 9111d3d3fc9666586a7531509e1b19bbe095a522 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 27 Jul 2017 16:33:36 +0200 Subject: [PATCH 211/342] [airos] used mapping for status in wpasupplicant.device configuration --- netjsonconfig/backends/airos/converters.py | 37 ++++++++++------------ 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 76aef74c4..09b559c06 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -703,25 +703,22 @@ class Wpasupplicant(AirOsConverter): netjson_key = 'interfaces' def _station_intermediate(self, original): - result = [] station_auth_protocols = available_mode_authentication['station'] - temp_dev = { 'profile': 'AUTO', 'status': 'enabled', 'driver': 'madwifi', 'devname': '', } + result = [] if original: head = original[0] + protocol = head['wireless']['encryption']['protocol'] temp_dev['devname'] = head['wireless']['radio'] + network = station_auth_protocols[protocol](head) - if 'encryption' in head['wireless']: - network = station_auth_protocols.get(head['wireless']['encryption']['protocol'])(head) - - else: - network = station_auth_protocols['none'](head) + if protocol == 'none': del temp_dev['driver'] del temp_dev['devname'] @@ -740,27 +737,27 @@ def _station_intermediate(self, original): def _access_point_intermediate(self, original): """ Intermediate representation for ``access_point`` mode - - wpasupplicant.device is missing when using the ``access_point`` mode - to the temp_dev will not be generated """ - result = [] ap_auth_protocols = available_mode_authentication['access_point'] - temp_dev = { 'profile': 'AUTO', - 'status': 'disabled', } + wpasupplicant_status = { + 'wpa2_personal': 'disabled', + 'none': 'enabled', + } + result = [] if original: head = original[0] - if 'encryption' in head: - network = ap_auth_protocols.get(head['encryption']['protocol'])(head) - result.append({'status': 'disabled'}) - else: - network = ap_auth_protocols['none'](head) - temp_dev['status'] = 'enabled' - result.append({'status': 'enabled'}) + protocol = head['wireless']['encryption']['protocol'] + status = wpasupplicant_status[protocol] + result.append({ + 'status': status + }) + temp_dev['status'] = status + network = ap_auth_protocols[protocol](head) + result.append({ 'device': [temp_dev], 'profile': [ From 02e8526440ccc717c7def13b9cb1618c6879d6df Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 27 Jul 2017 17:16:54 +0200 Subject: [PATCH 212/342] [airos] fixed keyerro in test for wpasupplicant --- tests/airos/test_wpasupplicant.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 0d815d3dc..d6a597b53 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -49,6 +49,7 @@ def test_no_encryption(self): "mode": "station", "ssid": "ap-ssid-example", "bssid": "00:11:22:33:44:55", + "encryption": {"protocol": "none"}, }, } ] From 287c33cd2a9b431845a513d918039f7cee675932 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 13:45:21 +0200 Subject: [PATCH 213/342] [airos] remove test for unnecessary netjson property --- tests/airos/test_aaa.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index 2a18faec6..db8a0a409 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -5,19 +5,6 @@ class TestAaaConverter(ConverterTest): backend = AaaAirOs - def test_aaa_key(self): - o = self.backend({ - "general": {}, - "interfaces": [], - }) - o.to_intermediate() - expected = [ - { - 'status': 'disabled', - }, - ] - self.assertEqualConfig(o.intermediate_data['aaa'], expected) - def test_ap_no_authentication(self): o = self.backend({ "interfaces": [ From 274f1ce95c17e1cb2bbb8a711c105b9117fb3282 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 13:46:01 +0200 Subject: [PATCH 214/342] [airos] fixed not valid netjson for aaa converter tests --- tests/airos/test_aaa.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index db8a0a409..b8c8d0237 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -15,6 +15,9 @@ def test_ap_no_authentication(self): "mode": "access_point", "radio": "ath0", "ssid": "i-like-pasta", + "encryption": { + "protocol": "none" + } }, }, ], @@ -87,6 +90,9 @@ def test_sta_no_authentication(self): "radio": "ath0", "ssid": "i-like-pasta", "bssid": "00:11:22:33:44:55", + "encryption": { + "protocol": "none", + }, }, }, ], From a0fb423472da1459f2637c895fb843d3c09b67a3 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 13:46:49 +0200 Subject: [PATCH 215/342] [airos] fixed expected output in aaa converter tests --- tests/airos/test_aaa.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index b8c8d0237..8844decac 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -65,6 +65,7 @@ def test_ap_psk_authentication(self): '1.radius.auth.1.port': 1812, '1.radius.auth.1.status': 'disabled', '1.status': 'enabled', + '1.wpa.psk': 'and-pizza-too', # here begins magic '1.radius.macacl.status': 'disabled', # move to radius method '1.ssid': 'i-like-pasta', @@ -74,7 +75,6 @@ def test_ap_psk_authentication(self): '1.wpa.1.pairwise': 'CCMP', '1.wpa.key.1.mgmt': 'WPA-PSK', '1.wpa.mode': 2, - '1.wpa.psk': 'and-pizza-too', } ] self.assertEqualConfig(o.intermediate_data['aaa'], expected) @@ -139,8 +139,8 @@ def test_sta_psk_authentication(self): '1.radius.acct.1.port': 1813, '1.radius.acct.1.status': 'disabled', '1.radius.auth.1.port': 1812, - '1.radius.auth.1.status': 'disabled', '1.status': 'disabled', + '1.wpa.psk': 'and-pizza-too', }, ] self.assertEqualConfig(o.intermediate_data['aaa'], expected) From d026f260a1915610d445d8bc0e74dec6c32b4b4d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 13:48:27 +0200 Subject: [PATCH 216/342] [airos] changed wireless method to property in aaa converter --- netjsonconfig/backends/airos/converters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 09b559c06..619c35e04 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -3,6 +3,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter +from .interface import bridge, wireless from .schema import default_ntp_servers from .wpasupplicant import available_mode_authentication @@ -57,11 +58,12 @@ def wpa2_personal(self): except IndexError: return {} + @property def wireless(self): """ Return all the wireless interfaces """ - return [i for i in get_copy(self.netjson, 'interfaces', []) if i['type'] == 'wireless'] + return wireless(get_copy(self.netjson, 'interfaces', [])) def status(self): """ From 674ac98b8879e1e418ea42890ae3c30ae212ef35 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 13:52:39 +0200 Subject: [PATCH 217/342] [airos] added netmode property to aaa converter --- netjsonconfig/backends/airos/converters.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 619c35e04..235fb2c02 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -58,6 +58,10 @@ def wpa2_personal(self): except IndexError: return {} + @property + def netmode(self): + return self.netjson.get('netmode', 'bridge') + @property def wireless(self): """ From 8e26953634363ccad3ab9d776e4feeace7749acf Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 14:36:26 +0200 Subject: [PATCH 218/342] [airos] moved interface releated functions into separate module --- netjsonconfig/backends/airos/interface.py | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 netjsonconfig/backends/airos/interface.py diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py new file mode 100644 index 000000000..e99a52815 --- /dev/null +++ b/netjsonconfig/backends/airos/interface.py @@ -0,0 +1,47 @@ +def bridge(interfaces): + """ + Return the bridge interfaces from the interfaces list + """ + return [i for i in interfaces if i['type'] == 'bridge'] + + +def mode(interface): + """ + Return wireless interface mode + """ + return interface['wireless']['mode'] + + +def protocol(interface): + """ + Return wireless interface encryption + """ + return interface['wireless']['encryption']['protocol'] + + +def psk(interface): + """ + Return the wpa2_personal psk + """ + return interface['wireless']['encryption']['key'] + + +def radio(interface): + """ + Return wireless interface's radio name + """ + return interface['wireless']['radio'] + + +def ssid(interface): + """ + Return the interface ssid + """ + return interface['wireless']['ssid'] + + +def wireless(interfaces): + """ + Return the wireless interfaces from the interfaces list + """ + return [i for i in interfaces if i['type'] == 'wireless'] From f6fa0394bcb64b7ce5879a3f2c3f8e4ab8e67488 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 14:38:30 +0200 Subject: [PATCH 219/342] [airos] moved logic for aaa section to separate module --- netjsonconfig/backends/airos/aaa.py | 109 ++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 netjsonconfig/backends/airos/aaa.py diff --git a/netjsonconfig/backends/airos/aaa.py b/netjsonconfig/backends/airos/aaa.py new file mode 100644 index 000000000..072a7bce4 --- /dev/null +++ b/netjsonconfig/backends/airos/aaa.py @@ -0,0 +1,109 @@ +from .interface import mode, protocol, psk, radio, ssid + + +def ap_none(interface): + return {} + + +def ap_psk(interface): + result = { + 'devname': radio(interface), + 'driver': 'madwifi', + 'ssid': ssid(interface), + 'wpa': { + '1.pairwise': 'CCMP', + 'key': [ + { + 'mgmt': 'WPA-PSK', + } + ], + 'mode': 2, + 'psk': psk(interface), + } + } + return result + + +def sta_none(interface): + return {} + + +def sta_psk(interface): + return { + 'wpa': { + 'psk': psk(interface), + } + } + +_profile = {} + +_profile_from_mode = { + 'access_point': { + 'none': ap_none, + 'wpa2_personal': ap_psk, + }, + 'station': { + 'none': sta_none, + 'wpa2_personal': sta_psk, + }, +} + +def profile_from_interface(interface): + profile = _profile.copy() + profile.update( + _profile_from_mode[mode(interface)][protocol(interface)](interface) + ) + return profile + +_status = {} + +_status_from_mode = { + 'access_point': { + 'none': { + 'status': 'disabled', + }, + 'wpa2_personal': { + 'status': 'enabled', + }, + }, + 'station': { + 'none': { + 'status': 'disabled', + }, + 'wpa2_personal': { + 'status': 'disabled', + }, + } +} + + +def status_from_interface(interface): + status = _status.copy() + status.update( + _status_from_mode[mode(interface)][protocol(interface)] + ) + return status + + +def bridge_devname(wireless_interface, bridge_interface): + """ + when in ``access_point`` with ``wpa2_personal`` authentication set also the + bridge interface name + + TODO: check if in ``netmode=router`` this happens again + """ + if mode(wireless_interface) == 'access_point' and protocol(wireless_interface) == 'wpa2_personal': + return { + 'br': { + 'devname': bridge_interface['name'], + } + } + else: + return {} + + +__all__ = [ + bridge_devname, + profile_from_interface, + status_from_interface, +] From 6dae05ad62767a98c410e81a8ce55b6a713efcc9 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 14:40:58 +0200 Subject: [PATCH 220/342] [airos] removed aaa method logic from converter --- netjsonconfig/backends/airos/converters.py | 50 ---------------------- 1 file changed, 50 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 235fb2c02..097ebdc80 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -15,26 +15,6 @@ def status(config, key='disabled'): return 'enabled' -def get_psk(interface): - t = { - 'wpa': { - 'psk': interface['wireless']['encryption']['key'], - }, - } - return t - - -def is_wpa2_personal(interface): - """ - returns True if the interface is configured with wpa2_personal - authentication - """ - try: - return interface['wireless']['encryption']['protocol'] == 'wpa2_personal' - except: - return False - - class AirOsConverter(BaseConverter): """ Always run the converter from NetJSON @@ -69,36 +49,6 @@ def wireless(self): """ return wireless(get_copy(self.netjson, 'interfaces', [])) - def status(self): - """ - The aaa.status value is enabled when the interface is in access_point mode - with wpa2_personal authentication - """ - try: - t = self.wireless()[0]['wireless'] - if t['mode'] == 'access_point' and t['encryption']['protocol'] == 'wpa2_personal': - return 'enabled' - else: - return 'disabled' - except: - # catch both KeyError and IndexError - return 'disabled' - - def ap_psk(self): - t = self.wireless()[0]['wireless'] - temp = { - 'radius.macacl.status': 'disabled', - 'ssid': t['ssid'], - 'devname': t['radio'], - 'driver': 'madwifi', - 'wpa.1.pairwise': 'CCMP', - 'wpa.key.1rmgmt': 'WPA-PSK', - 'wpa.mode': 2, - } - if t['mode'] == 'access_point' and t['encryption']['protocol'] == 'wpa2_personal': - return temp - else: - return {} def to_intermediate(self): result = [] From 85be0d112c3890cf70ce8ccbdcb575fd5d83e6be Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 14:44:16 +0200 Subject: [PATCH 221/342] [airos] added bridge property to aaa converter --- netjsonconfig/backends/airos/converters.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 097ebdc80..fa00d1ef9 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -28,15 +28,12 @@ def should_run_forward(cls, config): class Aaa(AirOsConverter): netjson_key = 'general' - def wpa2_personal(self): + @property + def bridge(self): """ - When using wpa2_personal the wifi password is written - in ``aaa.1.wpa.psk`` too + Return all the bridge interfaces """ - try: - return [get_psk(i) for i in self.wireless() if is_wpa2_personal(i)][0] - except IndexError: - return {} + return bridge(get_copy(self.netjson, 'interfaces', [])) @property def netmode(self): From d8575feadc31268e2da4bde51c21d54f5d09e35e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 14:45:01 +0200 Subject: [PATCH 222/342] [airos] added radius property to aaa converter moved radius configuration to separate module and get it from converter property --- netjsonconfig/backends/airos/converters.py | 5 ++ netjsonconfig/backends/airos/radius.py | 57 ++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 netjsonconfig/backends/airos/radius.py diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index fa00d1ef9..02da13d7a 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,6 +4,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .interface import bridge, wireless +from .radius import radius_from_interface from .schema import default_ntp_servers from .wpasupplicant import available_mode_authentication @@ -46,6 +47,10 @@ def wireless(self): """ return wireless(get_copy(self.netjson, 'interfaces', [])) + @property + def radius(self): + original = get_copy(self.netjson, 'radius', {}) + return original def to_intermediate(self): result = [] diff --git a/netjsonconfig/backends/airos/radius.py b/netjsonconfig/backends/airos/radius.py new file mode 100644 index 000000000..89a641b30 --- /dev/null +++ b/netjsonconfig/backends/airos/radius.py @@ -0,0 +1,57 @@ +from .interface import mode, protocol + +_radius = { + 'radius': { + 'acct': [ + { + 'port': 1813, + 'status': 'disabled', + } + ], + 'auth': [ + { + 'port': 1812, + }, + ], + }, + 'status': 'disabled', +} + +_radius_from_mode = { + 'access_point': { + 'none': {}, + 'wpa2_personal': { + 'radius': { + 'auth': [{ + 'port': 1812, + 'status': 'disabled', + }], + 'acct': [ + { + 'port': 1813, + 'status': 'disabled', + } + ], + 'macacl': { + 'status': 'disabled', + }, + }, + 'status': 'enabled', + }, + }, + 'station': { + 'none': {}, + 'wpa2_personal': {}, + } +} + +def radius_from_interface(interface): + radius = _radius.copy() + radius.update( + _radius_from_mode[mode(interface)][protocol(interface)] + ) + return radius + +__all__ = [ + radius_from_interface, +] From 3dc864f899b4ae7e0ad8748e7e2e813683cfa1ee Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 14:52:03 +0200 Subject: [PATCH 223/342] [airos] refactor aaa converter --- netjsonconfig/backends/airos/converters.py | 37 ++++++++-------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 02da13d7a..403b03c3a 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -3,6 +3,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter +from .aaa import bridge_devname, profile_from_interface, status_from_interface from .interface import bridge, wireless from .radius import radius_from_interface from .schema import default_ntp_servers @@ -53,30 +54,20 @@ def radius(self): return original def to_intermediate(self): + base = {} result = [] - temp = { - 'radius': { - 'acct': [ - { - 'port': 1813, - 'status': 'disabled', - }, - ], - 'auth': [ - { - 'port': 1812, - }, - ], - }, - 'status': 'disabled', - } - result.append({ - 'status': self.status(), - }) - result.append([ temp, ]) - w = self.wpa2_personal() - if w: - result.append([w]) + try: + wireless = self.wireless[0] + bridge = self.bridge[0] + base.update(profile_from_interface(wireless)) + base.update(status_from_interface(wireless)) + base.update(radius_from_interface(wireless)) + base.update(bridge_devname(wireless, bridge)) + except IndexError: + raise Exception('input is missing a wireless or bridge interface') + result.append(status_from_interface(wireless)) + result.append([base]) + return (('aaa', result),) From 5755de0a9ca05afad3c28500531195cd4feb3181 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 15:02:12 +0200 Subject: [PATCH 224/342] [airos] added bridge interface to access_point test As we need the br.devname key in the configuration we need to list a bridge interface to take it from --- tests/airos/test_aaa.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index 8844decac..2796c4189 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -52,6 +52,13 @@ def test_ap_psk_authentication(self): }, }, }, + { + "type": "bridge", + "name": "br0", + "bridge_members": [ + "wlan0", + ], + }, ], }) o.to_intermediate() From fdcf10d0ede9c384c6e040053f95cde1c6c8a9c4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 15:15:11 +0200 Subject: [PATCH 225/342] [airos] make flake happier --- netjsonconfig/backends/airos/aaa.py | 3 +++ netjsonconfig/backends/airos/converters.py | 12 ++++++++---- netjsonconfig/backends/airos/radius.py | 2 ++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/netjsonconfig/backends/airos/aaa.py b/netjsonconfig/backends/airos/aaa.py index 072a7bce4..33f96b472 100644 --- a/netjsonconfig/backends/airos/aaa.py +++ b/netjsonconfig/backends/airos/aaa.py @@ -35,6 +35,7 @@ def sta_psk(interface): } } + _profile = {} _profile_from_mode = { @@ -48,6 +49,7 @@ def sta_psk(interface): }, } + def profile_from_interface(interface): profile = _profile.copy() profile.update( @@ -55,6 +57,7 @@ def profile_from_interface(interface): ) return profile + _status = {} _status_from_mode = { diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 403b03c3a..e6448cdd7 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -58,13 +58,18 @@ def to_intermediate(self): result = [] try: wireless = self.wireless[0] - bridge = self.bridge[0] base.update(profile_from_interface(wireless)) base.update(status_from_interface(wireless)) base.update(radius_from_interface(wireless)) - base.update(bridge_devname(wireless, bridge)) except IndexError: raise Exception('input is missing a wireless or bridge interface') + + try: + bridge = self.bridge[0] + base.update(bridge_devname(wireless, bridge)) + except IndexError: + pass + result.append(status_from_interface(wireless)) result.append([base]) @@ -305,7 +310,6 @@ def to_intermediate(self): class Ntpclient(AirOsConverter): netjson_key = 'ntp' - def ntp_status(self, ntp): if ntp.get('enabled', True): return 'enabled' @@ -639,7 +643,7 @@ def to_intermediate(self): 'signal_led4': 15, 'signal_led_status': 'enabled', 'ssid': w['wireless']['ssid'], - 'ap': w['wireless'].get('bssid',''), + 'ap': w['wireless'].get('bssid', ''), 'status': status(w), 'wds': {'status': 'enabled'}, }) diff --git a/netjsonconfig/backends/airos/radius.py b/netjsonconfig/backends/airos/radius.py index 89a641b30..ca9d323bd 100644 --- a/netjsonconfig/backends/airos/radius.py +++ b/netjsonconfig/backends/airos/radius.py @@ -45,6 +45,7 @@ } } + def radius_from_interface(interface): radius = _radius.copy() radius.update( @@ -52,6 +53,7 @@ def radius_from_interface(interface): ) return radius + __all__ = [ radius_from_interface, ] From 2f6a8a37ebb422cd072fcaee1b951e4f7f11017a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 17:26:22 +0200 Subject: [PATCH 226/342] [airos] used functions from interface module where possible --- netjsonconfig/backends/airos/converters.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index e6448cdd7..5f2219888 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,7 +4,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface -from .interface import bridge, wireless +from .interface import bridge, bssid, hidden_ssid, protocol, radio, ssid, wireless from .radius import radius_from_interface from .schema import default_ntp_servers from .wpasupplicant import available_mode_authentication @@ -624,8 +624,8 @@ def to_intermediate(self): for w in original: wireless_list.append({ 'addmtikie': 'enabled', - 'devname': w['wireless']['radio'], - 'hide_ssid': 'enabled' if w['wireless'].get('hidden') else 'disabled', + 'devname': radio(w), + 'hide_ssid': hidden_ssid(w), 'l2_isolation': 'disabled', 'mac_acl': { 'policy': 'allow', @@ -642,8 +642,8 @@ def to_intermediate(self): 'signal_led3': 25, 'signal_led4': 15, 'signal_led_status': 'enabled', - 'ssid': w['wireless']['ssid'], - 'ap': w['wireless'].get('bssid', ''), + 'ssid': ssid(w), + 'ap': bssid(w), 'status': status(w), 'wds': {'status': 'enabled'}, }) @@ -667,11 +667,11 @@ def _station_intermediate(self, original): if original: head = original[0] - protocol = head['wireless']['encryption']['protocol'] - temp_dev['devname'] = head['wireless']['radio'] - network = station_auth_protocols[protocol](head) + proto = protocol(head) + temp_dev['devname'] = radio(head) + network = station_auth_protocols[proto](head) - if protocol == 'none': + if proto == 'none': del temp_dev['driver'] del temp_dev['devname'] From 48f88f6b7cb47a6205388dc2f09dacf160ddc037 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 17:27:20 +0200 Subject: [PATCH 227/342] [airos] used wireless property in converter where needed --- netjsonconfig/backends/airos/converters.py | 30 ++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 5f2219888..28fa5fa89 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -615,13 +615,17 @@ def to_intermediate(self): class Wireless(AirOsConverter): netjson_key = 'interfaces' + @property + def wireless(self): + """ + Return all the wireless interfaces + """ + return wireless(get_copy(self.netjson, 'interfaces', [])) + def to_intermediate(self): result = [] - original = [ - i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'wireless' - ] wireless_list = [] - for w in original: + for w in self.wireless: wireless_list.append({ 'addmtikie': 'enabled', 'devname': radio(w), @@ -655,6 +659,13 @@ def to_intermediate(self): class Wpasupplicant(AirOsConverter): netjson_key = 'interfaces' + @property + def wireless(self): + """ + Return all the wireless interfaces + """ + return wireless(get_copy(self.netjson, 'interfaces', [])) + def _station_intermediate(self, original): station_auth_protocols = available_mode_authentication['station'] temp_dev = { @@ -733,11 +744,10 @@ def secondary_network(self): } def to_intermediate(self): - original = [ - i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'wireless' - ] - if original: - head = original[0] + try: + head = self.wireless[0] # call either ``_station_intermediate`` or ``_access_point_intermediate`` # and return the result - return getattr(self, '_%s_intermediate' % head['wireless']['mode'])(original) + return getattr(self, '_%s_intermediate' % head['wireless']['mode'])(self.wireless) + except IndexError: + raise Warning('Zero wireless interface found') From a271621d0d16af0e2afa644ae66fb5886a15cf75 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 17:27:56 +0200 Subject: [PATCH 228/342] [airos] added functions for bssid and hidden_ssid values from interface --- netjsonconfig/backends/airos/interface.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index e99a52815..3f21f57d8 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -5,6 +5,16 @@ def bridge(interfaces): return [i for i in interfaces if i['type'] == 'bridge'] +def hidden_ssid(interface): + """ + Return wether the ssid is hidden + """ + if interface['wireless'].get('hidden', False): + return 'enabled' + else: + return 'disabled' + + def mode(interface): """ Return wireless interface mode @@ -33,6 +43,13 @@ def radio(interface): return interface['wireless']['radio'] +def bssid(interface): + """ + Return the interface bssid + """ + return interface['wireless'].get('bssid', '') + + def ssid(interface): """ Return the interface ssid From f310823cb6dcaf0a8017df676d2d43072ba15628 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 17:28:45 +0200 Subject: [PATCH 229/342] [airos] added bridge interface to input in aaa tests --- tests/airos/test_aaa.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index 2796c4189..89ff78d34 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -20,6 +20,13 @@ def test_ap_no_authentication(self): } }, }, + { + "type": "bridge", + "name": "br0", + "bridge_members": [ + "wlan0", + ], + }, ], }) o.to_intermediate() @@ -102,6 +109,13 @@ def test_sta_no_authentication(self): }, }, }, + { + "type": "bridge", + "name": "br0", + "bridge_members": [ + "wlan0", + ], + }, ], }) o.to_intermediate() @@ -135,6 +149,13 @@ def test_sta_psk_authentication(self): }, }, }, + { + "type": "bridge", + "name": "br0", + "bridge_members": [ + "wlan0", + ], + }, ], }) o.to_intermediate() From 2204b2589edff6a839ec38d2de945cb89e311262 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 17:29:25 +0200 Subject: [PATCH 230/342] [airos] handled comments in aaa converter output --- tests/airos/test_aaa.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index 89ff78d34..9b7fe938e 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -80,10 +80,9 @@ def test_ap_psk_authentication(self): '1.radius.auth.1.status': 'disabled', '1.status': 'enabled', '1.wpa.psk': 'and-pizza-too', - # here begins magic - '1.radius.macacl.status': 'disabled', # move to radius method + '1.radius.macacl.status': 'disabled', '1.ssid': 'i-like-pasta', - '1.br.devname': 'br0', # only in bridge mode? + '1.br.devname': 'br0', # only in bridge mode? '1.devname': 'ath0', '1.driver': 'madwifi', '1.wpa.1.pairwise': 'CCMP', From 9498d6694cd173c8ff4000c72b9dd3895c3dc580 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 28 Jul 2017 17:33:20 +0200 Subject: [PATCH 231/342] [airos] removed whitespace for poor little flake --- tests/airos/test_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/airos/test_gui.py b/tests/airos/test_gui.py index 460b82a9d..3444c482b 100644 --- a/tests/airos/test_gui.py +++ b/tests/airos/test_gui.py @@ -24,7 +24,7 @@ def test_gui_key(self): def test_language(self): o = self.backend({ 'gui': { - 'language' : 'it_IT', + 'language': 'it_IT', }, }) o.to_intermediate() From f87efa037ba714100dfc24aa127e4d2023006395 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 31 Jul 2017 10:59:03 +0200 Subject: [PATCH 232/342] [airos] added radio blob configuration and updated radio converter --- netjsonconfig/backends/airos/converters.py | 15 +++--- netjsonconfig/backends/airos/radio.py | 54 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 netjsonconfig/backends/airos/radio.py diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 28fa5fa89..ad1d7baea 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -7,6 +7,7 @@ from .interface import bridge, bssid, hidden_ssid, protocol, radio, ssid, wireless from .radius import radius_from_interface from .schema import default_ntp_servers +from .radio import radio_device_base, radio_configuration from .wpasupplicant import available_mode_authentication @@ -353,14 +354,16 @@ def to_intermediate(self): original = get_copy(self.netjson, self.netjson_key, []) radios = [] for r in original: - radios.append({ + base = radio_device_base.copy() + user_configs = { 'devname': r['name'], - 'status': status(r), - 'txpower': r.get('tx_power', ''), - 'chanbw': r.get('channel_width', ''), - }) + 'txpower': r.get('tx_power', 24), + 'chanbw': r.get('channel_width', 0), + } + base.update(user_configs) + radios.append(base) result.append(radios) - result.append({'status': 'enabled'}) + result.append(radio_configuration) return (('radio', result),) diff --git a/netjsonconfig/backends/airos/radio.py b/netjsonconfig/backends/airos/radio.py new file mode 100644 index 000000000..8075b9b15 --- /dev/null +++ b/netjsonconfig/backends/airos/radio.py @@ -0,0 +1,54 @@ +radio_device_base = { + 'ack': {'auto': 'enabled'}, + 'ackdistance': 643, + 'acktimeout': 35, + 'ampdu': { + 'frames': 32, + 'status': 'enabled', + }, + 'antenna': { + 'gain': 3, + 'id': 2, + }, + 'atpc': { + 'sta.status': 'enabled', + 'status': 'disabled', + 'threshold': 36, + }, + 'cable': {'loss': 0}, + 'center': [{'freq': 0}], + 'chanbw': 0, + 'cmsbias': 0, + 'countrycode': 380, + 'cwm': { + 'enable': 0, + 'mode': 1, + }, + 'devname': 'ath0', + 'dfs': {'status': 'enabled'}, + 'freq': 0, + 'ieee_mode': 'auto', + 'low_txpower_mode': 'disabled', + 'mode': 'managed', # ap => master, sta => managed + 'obey': 'enabled', + 'polling': 'enabled', + 'polling_11ac_11n_compat': 0, + 'polling_ff_dl_ratio': 50, + 'polling_ff_dur': 0, + 'polling_ff_timing': 0, + 'pollingnoack': 0, + 'pollingpri': 2, + 'ptpmode': 1, + 'reg_obey': 'enabled', + 'rx_sensitivity': -96, + 'scan_list': {'status': 'disabled'}, + 'scanbw': {'status': 'disabled'}, + 'status': 'enabled', # cannot disable + 'subsystemid': 0xe7f5, + 'txpower': 24, +} + +radio_configuration = { + 'status': 'enabled', + 'countrycode': 380, +} From e958199f4728301390d7d3840b6ce213eafe4585 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 31 Jul 2017 13:16:13 +0200 Subject: [PATCH 233/342] [airos] ordered functions in interface module --- netjsonconfig/backends/airos/interface.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index 3f21f57d8..0028ef844 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -1,3 +1,6 @@ +from ipaddress import ip_interface + + def bridge(interfaces): """ Return the bridge interfaces from the interfaces list @@ -5,6 +8,13 @@ def bridge(interfaces): return [i for i in interfaces if i['type'] == 'bridge'] +def bssid(interface): + """ + Return the interface bssid + """ + return interface['wireless'].get('bssid', '') + + def hidden_ssid(interface): """ Return wether the ssid is hidden @@ -43,11 +53,8 @@ def radio(interface): return interface['wireless']['radio'] -def bssid(interface): """ - Return the interface bssid """ - return interface['wireless'].get('bssid', '') def ssid(interface): From c5344084d8431440f00b75ce54c5038d084a3060 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 31 Jul 2017 13:17:15 +0200 Subject: [PATCH 234/342] [airos] added split_cidr function to interface module --- netjsonconfig/backends/airos/interface.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index 0028ef844..aa08be47a 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -53,8 +53,12 @@ def radio(interface): return interface['wireless']['radio'] +def split_cidr(address): """ + Return the address in dict format """ + network = ip_interface('{addr}/{mask}'.format(addr=address['address'], mask=address['mask'])) + return {'ip': network.ip, 'netmask': network.netmask} def ssid(interface): From c2e01e178f5d97789129b55cb028682628e36826 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 31 Jul 2017 13:18:02 +0200 Subject: [PATCH 235/342] [airos] moved cidr logic to interface module --- netjsonconfig/backends/airos/converters.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index ad1d7baea..eeb02416a 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,7 +4,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface -from .interface import bridge, bssid, hidden_ssid, protocol, radio, ssid, wireless +from .interface import bridge, bssid, hidden_ssid, protocol, radio, split_cidr, ssid, wireless from .radius import radius_from_interface from .schema import default_ntp_servers from .radio import radio_device_base, radio_configuration @@ -268,7 +268,7 @@ def to_intermediate(self): base['flowcontrol'] = self.flowcontrol_status(interface) if interface['type'] == 'wireless': - base['devname'] = interface['wireless']['radio'] + base['devname'] = radio(interface) addresses = interface.get('addresses') if addresses: @@ -282,10 +282,7 @@ def to_intermediate(self): if addr['proto'] == 'dhcp': temp['autoip'] = {'status': 'enabled'} else: - ip_and_mask = '%s/%d' % (addr['address'], addr['mask']) - network = ip_interface(ip_and_mask) - temp['ip'] = str(network.ip) - temp['netmask'] = str(network.netmask) + temp.update(split_cidr(addr)) interfaces.append(temp) else: # an interface without address From b145d1c0d81780d6d1e1c7a30477e027c664066c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 31 Jul 2017 13:18:28 +0200 Subject: [PATCH 236/342] [airos] updated defaults for pwdog converter --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index eeb02416a..141fa93cf 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -338,7 +338,7 @@ def to_intermediate(self): 'delay': 300, 'period': 300, 'retry': 3, - 'status': 'enabled', + 'status': 'disabled', }) return (('pwdog', result),) From 41c03126794bb0369b9fb4f64b58842852ae18df Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 31 Jul 2017 15:40:32 +0200 Subject: [PATCH 237/342] [airos] change radio converter to always run --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 141fa93cf..e3f44a1a7 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -343,7 +343,7 @@ def to_intermediate(self): return (('pwdog', result),) -class Radio(BaseConverter): +class Radio(AirOsConverter): netjson_key = 'radios' def to_intermediate(self): From 0c1f2e94b59a622e27cb0f930d017d07f2c58be5 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 31 Jul 2017 15:40:56 +0200 Subject: [PATCH 238/342] [airos] remove channel brandwidth setter as there is no support for setting the channel brandwidth --- netjsonconfig/backends/airos/converters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index e3f44a1a7..048ed7c84 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -355,7 +355,6 @@ def to_intermediate(self): user_configs = { 'devname': r['name'], 'txpower': r.get('tx_power', 24), - 'chanbw': r.get('channel_width', 0), } base.update(user_configs) radios.append(base) From 1baa0149f176d287a970be5e8459147ac439ad26 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 31 Jul 2017 15:41:44 +0200 Subject: [PATCH 239/342] [airos] switched system.date to disabled by default --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 048ed7c84..0375a3a8c 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -524,7 +524,7 @@ def to_intermediate(self): 'version': 0, }, 'date': { - 'status': 'enabled', + 'status': 'disabled', }, 'external': { 'reset': 'enabled', From 95554d869927a34de5f0faf96408b833e5f7dde0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 31 Jul 2017 16:04:10 +0200 Subject: [PATCH 240/342] [airos] fixed error with intermediate representation --- netjsonconfig/backends/airos/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index aa08be47a..9d8190d63 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -58,7 +58,7 @@ def split_cidr(address): Return the address in dict format """ network = ip_interface('{addr}/{mask}'.format(addr=address['address'], mask=address['mask'])) - return {'ip': network.ip, 'netmask': network.netmask} + return {'ip': str(network.ip), 'netmask': str(network.netmask)} def ssid(interface): From 9936b721c0f860544210b650bf03be889cf64161 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 11:27:21 +0200 Subject: [PATCH 241/342] [airos] updated ebtables config for router mode --- netjsonconfig/backends/airos/converters.py | 30 ++++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 0375a3a8c..940fa4ee4 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -140,18 +140,26 @@ def to_intermediate(self): class Ebtables(AirOsConverter): netjson_key = 'general' + _base = { + 'sys': { + 'fw': { + 'status': 'disabled', + }, + 'status': 'enabled' + }, + 'status': 'enabled' + } + + def bridge_intermediate(self): + base = self._base.copy() + return [base] + + def router_intermediate(self): + return [{}] + def to_intermediate(self): - result = [ - { - 'sys': { - 'fw': { - 'status': 'disabled', - }, - 'status': 'enabled' - }, - 'status': 'enabled' - } - ] + netmode = get_copy(self.netjson, 'netmode') + result = getattr(self, '{netmode}_intermediate'.format(netmode=netmode))() return (('ebtables', result),) From f9a47c104a8078d6ea3d3f732a75fd34cb0c1a22 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 11:27:48 +0200 Subject: [PATCH 242/342] [airos] updated iptables section for router mode --- netjsonconfig/backends/airos/converters.py | 41 +++++++++++++++++----- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 940fa4ee4..e17ddc7e4 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -214,16 +214,39 @@ def to_intermediate(self): class Iptables(AirOsConverter): netjson_key = 'general' - def to_intermediate(self): - result = [ - { - 'sys': { - 'portfw': {'status': 'disabled'}, + _base = { + 'sys': { + 'portfw': {'status': 'disabled'}, + 'status': 'enabled', + }, + 'status': 'disabled' + } + + def bridge_intermediate(self): + base = self._base.copy() + return [base] + + def router_intermediate(self): + base = self._base.copy() + base.update({ + 'status': 'enabled', + }) + base['sys'].update({ + 'fw': {'status': 'disabled'}, + 'mgmt': [ + { + 'devname': 'br0', 'status': 'enabled', - }, - 'status': 'disabled' - } - ] + } + ], + 'mgmt.status': 'enabled', + }) + + return [base] + + def to_intermediate(self): + netmode = get_copy(self.netjson, 'netmode') + result = getattr(self, '{netmode}_intermediate'.format(netmode=netmode))() return (('iptables', result),) From 4713f2a67ba66733fc930f335cbf1866b59aab22 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 11:51:16 +0200 Subject: [PATCH 243/342] [airos] added tshaper and unms converters to airos backend --- netjsonconfig/backends/airos/airos.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index f8bcc44e0..7a74e3f8b 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -3,8 +3,8 @@ from .converters import (Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, Httpd, Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, - System, Telnetd, Update, Users, Vlan, Wireless, - Wpasupplicant) + System, Telnetd, Tshaper, Unms, Update, Users, + Vlan, Wireless, Wpasupplicant) from .intermediate import flatten, intermediate_to_list from .renderers import AirOsRenderer from .schema import schema @@ -45,6 +45,8 @@ class AirOs(BaseBackend): Syslog, System, Telnetd, + Tshaper, + Unms, Update, Users, Vlan, From 5f599ef0ad8e8569c3d4ead724e77e83719f8653 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 12:00:11 +0200 Subject: [PATCH 244/342] [airos] added default values for netmode in ebtables and iptables --- netjsonconfig/backends/airos/converters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index e17ddc7e4..065661457 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -158,7 +158,7 @@ def router_intermediate(self): return [{}] def to_intermediate(self): - netmode = get_copy(self.netjson, 'netmode') + netmode = get_copy(self.netjson, 'netmode', 'bridge') result = getattr(self, '{netmode}_intermediate'.format(netmode=netmode))() return (('ebtables', result),) @@ -245,7 +245,7 @@ def router_intermediate(self): return [base] def to_intermediate(self): - netmode = get_copy(self.netjson, 'netmode') + netmode = get_copy(self.netjson, 'netmode', 'bridge') result = getattr(self, '{netmode}_intermediate'.format(netmode=netmode))() return (('iptables', result),) From de6686fc6a303a7b498c377d34dfd6c145efbf11 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 12:00:40 +0200 Subject: [PATCH 245/342] [airos] moved autonegotiation and flowcontrol method to interface module --- netjsonconfig/backends/airos/converters.py | 26 +++----------------- netjsonconfig/backends/airos/interface.py | 28 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 065661457..7c60aa54f 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,7 +4,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface -from .interface import bridge, bssid, hidden_ssid, protocol, radio, split_cidr, ssid, wireless +from .interface import autonegotiation, bridge, bssid, flowcontrol, hidden_ssid, protocol, radio, split_cidr, ssid, wireless from .radius import radius_from_interface from .schema import default_ntp_servers from .radio import radio_device_base, radio_configuration @@ -260,26 +260,6 @@ def type_to_role(self, typestr): } return roles.get(typestr, '') - def autoneg_status(self, interface): - if interface.get('autoneg'): - return 'enabled' - else: - return 'disabled' - - def flowcontrol_status(self, interface): - if interface.get('flowcontrol'): - status = 'enabled' - else: - status = 'disabled' - return { - 'rx': { - 'status': status, - }, - 'tx': { - 'status': status, - }, - } - def to_intermediate(self): result = [] interfaces = [] @@ -294,9 +274,9 @@ def to_intermediate(self): } # handle interface type quirks if interface['type'] == 'ethernet' and '.' not in interface['name']: - base['autoneg'] = self.autoneg_status(interface) + base['autoneg'] = autonegotiation(interface) - base['flowcontrol'] = self.flowcontrol_status(interface) + base['flowcontrol'] = flowcontrol(interface) if interface['type'] == 'wireless': base['devname'] = radio(interface) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index 9d8190d63..adfdc43d3 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -1,6 +1,16 @@ from ipaddress import ip_interface +def autonegotiation(interface): + """ + Return the configuration for ``autoneg`` on interface + """ + if interface.get('autoneg'): + return 'enabled' + else: + return 'disabled' + + def bridge(interfaces): """ Return the bridge interfaces from the interfaces list @@ -15,6 +25,24 @@ def bssid(interface): return interface['wireless'].get('bssid', '') +def flowcontrol(interface): + """ + Return the configuration for ``flowcontrol`` on interface + """ + if interface.get('flowcontrol'): + status = 'enabled' + else: + status = 'disabled' + return { + 'rx': { + 'status': status, + }, + 'tx': { + 'status': status, + }, + } + + def hidden_ssid(interface): """ Return wether the ssid is hidden From d23f1ab9fdb96f5f791d673b857cf530990c85e3 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 12:02:56 +0200 Subject: [PATCH 246/342] [airos] updated test expected value for pwdog converter --- tests/airos/test_pwdog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/airos/test_pwdog.py b/tests/airos/test_pwdog.py index df616ec78..30b5e2ad7 100644 --- a/tests/airos/test_pwdog.py +++ b/tests/airos/test_pwdog.py @@ -15,7 +15,7 @@ def test_ntp_key(self): 'delay': 300, 'period': 300, 'retry': 3, - 'status': 'enabled', + 'status': 'disabled', }, ] From 8e46afdf4ecf5d094c05b622ae2d060c37088a3c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 12:18:43 +0200 Subject: [PATCH 247/342] [airos] moved vlan interface selection to property --- netjsonconfig/backends/airos/converters.py | 11 ++++++----- netjsonconfig/backends/airos/interface.py | 7 +++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 7c60aa54f..015ab6762 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,7 +4,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface -from .interface import autonegotiation, bridge, bssid, flowcontrol, hidden_ssid, protocol, radio, split_cidr, ssid, wireless +from .interface import autonegotiation, bridge, bssid, flowcontrol, hidden_ssid, protocol, radio, split_cidr, ssid, stp, wireless from .radius import radius_from_interface from .schema import default_ntp_servers from .radio import radio_device_base, radio_configuration @@ -604,13 +604,14 @@ def to_intermediate(self): class Vlan(AirOsConverter): netjson_key = 'interfaces' + @property + def vlan(self): + return vlan(get_copy(self.netjson, self.netjson_key, [])) + def to_intermediate(self): result = [] - original = [ - i for i in get_copy(self.netjson, self.netjson_key, []) if '.' in i['name'] - ] vlans = [] - for v in original: + for v in self.vlan: vlans.append({ 'comment': v.get('comment', ''), 'devname': v['name'].split('.')[0], diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index adfdc43d3..1ed15dfcb 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -96,6 +96,13 @@ def ssid(interface): return interface['wireless']['ssid'] +def vlan(interfaces): + """ + Return the vlan interfaces from the interfaces list + """ + return [i for i in interfaces if '.' in i['name']] + + def wireless(interfaces): """ Return the wireless interfaces from the interfaces list From e322087cfcc1944bbd730c9da218370dee81fb98 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 12:19:42 +0200 Subject: [PATCH 248/342] [airos] moved stp status function to interface module --- netjsonconfig/backends/airos/converters.py | 9 ++------- netjsonconfig/backends/airos/interface.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 015ab6762..6fec42a98 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,7 +4,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface -from .interface import autonegotiation, bridge, bssid, flowcontrol, hidden_ssid, protocol, radio, split_cidr, ssid, stp, wireless +from .interface import autonegotiation, bridge, bssid, flowcontrol, hidden_ssid, protocol, radio, split_cidr, ssid, stp, vlan, wireless from .radius import radius_from_interface from .schema import default_ntp_servers from .radio import radio_device_base, radio_configuration @@ -80,11 +80,6 @@ def to_intermediate(self): class Bridge(AirOsConverter): netjson_key = 'interfaces' - def stp_status(self, interface): - if interface.get('stp', False): - return 'enabled' - else: - return 'disabled' def to_intermediate(self): result = [] @@ -104,7 +99,7 @@ def to_intermediate(self): 'devname': interface['name'], 'port': bridge_ports, 'status': status(interface), - 'stp': {'status': self.stp_status(interface)} + 'stp': {'status': stp(interface)} }) result.append(bridges) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index 1ed15dfcb..4890c511e 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -96,6 +96,16 @@ def ssid(interface): return interface['wireless']['ssid'] +def stp(interface): + """ + Return wether the spanning tree protocol is enabled + """ + if interface.get('stp', False): + return 'enabled' + else: + return 'disabled' + + def vlan(interfaces): """ Return the vlan interfaces from the interfaces list From 5717299961a7d0de497f09855226f995945daac9 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 12:20:03 +0200 Subject: [PATCH 249/342] [airos] added bridge property to bridge converter --- netjsonconfig/backends/airos/converters.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 6fec42a98..0f2a41340 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -80,14 +80,14 @@ def to_intermediate(self): class Bridge(AirOsConverter): netjson_key = 'interfaces' + @property + def bridge(self): + return bridge(get_copy(self.netjson, self.netjson_key, [])) def to_intermediate(self): result = [] - original = [ - i for i in get_copy(self.netjson, self.netjson_key, []) if i['type'] == 'bridge' - ] bridges = [] - for interface in original: + for interface in self.bridge: bridge_ports = [] for port in interface.get('bridge_members', []): bridge_ports.append({ From f5d0c3767e8a86c8a88a41692dbbb0b463332009 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 17:20:30 +0200 Subject: [PATCH 250/342] [airos] added radio module for radio configuration generation --- netjsonconfig/backends/airos/converters.py | 28 ++++++++++------ netjsonconfig/backends/airos/radio.py | 39 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 0f2a41340..2ab269f45 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,10 +4,10 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface -from .interface import autonegotiation, bridge, bssid, flowcontrol, hidden_ssid, protocol, radio, split_cidr, ssid, stp, vlan, wireless +from .interface import autonegotiation, bridge, bssid, flowcontrol, hidden_ssid, mode, protocol, radio, split_cidr, ssid, stp, vlan, wireless +from .radio import radio_available_mode, radio_configuration from .radius import radius_from_interface from .schema import default_ntp_servers -from .radio import radio_device_base, radio_configuration from .wpasupplicant import available_mode_authentication @@ -352,18 +352,24 @@ def to_intermediate(self): class Radio(AirOsConverter): netjson_key = 'radios' + @property + def radio(self): + return get_copy(self.netjson, self.netjson_key, []) + + @property + def wireless(self): + return wireless(get_copy(self.netjson, 'interfaces', [])) + def to_intermediate(self): result = [] - original = get_copy(self.netjson, self.netjson_key, []) radios = [] - for r in original: - base = radio_device_base.copy() - user_configs = { - 'devname': r['name'], - 'txpower': r.get('tx_power', 24), - } - base.update(user_configs) - radios.append(base) + wireless = {radio(w): w for w in self.wireless} + for logic in self.radio: + w = wireless.get(logic['name']) + if w: + user_config = radio_available_mode[mode(w)](logic) + radios.append(user_config) + result.append(radios) result.append(radio_configuration) return (('radio', result),) diff --git a/netjsonconfig/backends/airos/radio.py b/netjsonconfig/backends/airos/radio.py index 8075b9b15..1d2613aa2 100644 --- a/netjsonconfig/backends/airos/radio.py +++ b/netjsonconfig/backends/airos/radio.py @@ -52,3 +52,42 @@ 'status': 'enabled', 'countrycode': 380, } + + +def access_point(radio): + """ + Return the configuration for a radio device whose wireless + interface is in ``access_point`` mode + """ + base = _radio_device_base.copy() + base.update({ + 'devname': radio['name'], + 'chanbw': 80, + 'ieee_mode': '11acvht80', + 'mode': 'master', + }) + return base + + +def station(radio): + """ + Return the configuration for a radio device whose wireless + interface is in ``station`` mode + """ + base = _radio_device_base.copy() + base.update({ + 'devname': radio['name'], + 'txpower': radio.get('tx_power', 24), + }) + return base + + +radio_available_mode = { + 'access_point': access_point, + 'station': station, +} + +__all__ = [ + radio_available_mode, + radio_configuration, +] From 232cbca342edfaf656b081d072ee7c82cfe83ff8 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 17:21:16 +0200 Subject: [PATCH 251/342] [airos] added coverter for upnp daemon --- netjsonconfig/backends/airos/converters.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 2ab269f45..bfd97fdf4 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -581,6 +581,18 @@ def to_intermediate(self): return (('update', result),) +class Upnpd(AirOsConverter): + @classmethod + def should_run_forward(cls, config): + if config.get('netmode', 'bridge') == 'bridge': + return False + else: + return True + + def to_intermediate(self): + return (('upnpd', [{'status': 'disabled'}]),) + + class Users(AirOsConverter): netjson_key = 'user' From d356ce10fb902bcf2bc20e6cc9dfb393ff9d276d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 1 Aug 2017 17:25:18 +0200 Subject: [PATCH 252/342] [airos] updated expected value for radio converter test --- tests/airos/test_radio.py | 137 ++++++++++++++++++++++++++++++++++---- 1 file changed, 125 insertions(+), 12 deletions(-) diff --git a/tests/airos/test_radio.py b/tests/airos/test_radio.py index 252575d76..fdcc8c703 100644 --- a/tests/airos/test_radio.py +++ b/tests/airos/test_radio.py @@ -1,7 +1,10 @@ from .mock import ConverterTest, RadioAirOs -class TestRadioConverter(ConverterTest): +class TestRadioStationConverter(ConverterTest): + """ + Test radio device configuration for ``station`` wireless lan + """ backend = RadioAirOs @@ -13,6 +16,7 @@ def test_no_radio(self): expected = [ { 'status': 'enabled', + 'countrycode': 380, }, ] @@ -20,12 +24,26 @@ def test_no_radio(self): def test_active_radio(self): o = self.backend({ + "interfaces": [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'radio': 'ath0', + 'mode': 'station', + 'ssid': 'ubnt', + 'bssid': '00:11:22:33:44:55', + 'encryption': { + 'protocol': 'none', + }, + }, + }, + ], "radios": [ { 'name': 'ath0', 'channel': 1, 'channel_width': 20, - 'disabled': False, 'protocol': '802.11n', } ] @@ -33,40 +51,135 @@ def test_active_radio(self): o.to_intermediate() expected = [ { - '1.chanbw': 20, + '1.ack.auto': 'enabled', + '1.ackdistance': 643, + '1.acktimeout': 35, + '1.ampdu.frames': 32, + '1.ampdu.status': 'enabled', + '1.antenna.gain': 3, + '1.antenna.id': 2, + '1.atpc.sta.status': 'enabled', + '1.atpc.status': 'disabled', + '1.atpc.threshold': 36, + '1.cable.loss': 0, + '1.center.1.freq': 0, + '1.chanbw': 0, + '1.cmsbias': 0, + '1.countrycode': 380, + '1.cwm.enable': 0, + '1.cwm.mode': 1, '1.devname': 'ath0', + '1.dfs.status': 'enabled', + '1.freq': 0, + '1.ieee_mode': 'auto', + '1.low_txpower_mode': 'disabled', + '1.mode': 'managed', + '1.obey': 'enabled', + '1.polling': 'enabled', + '1.polling_11ac_11n_compat': 0, + '1.polling_ff_dl_ratio': 50, + '1.polling_ff_dur': 0, + '1.polling_ff_timing': 0, + '1.pollingnoack': 0, + '1.pollingpri': 2, + '1.ptpmode': 1, + '1.reg_obey': 'enabled', + '1.rx_sensitivity': -96, + '1.scan_list.status': 'disabled', + '1.scanbw.status': 'disabled', '1.status': 'enabled', - '1.txpower': '', + '1.subsystemid': 0xe7f5, + '1.txpower': 24, }, { + 'countrycode': 380, 'status': 'enabled', - }, + } ] self.assertEqualConfig(o.intermediate_data['radio'], expected) - def test_inactive_radio(self): + +class TestRadioAccessPointConverter(ConverterTest): + """ + Test radio device configuration for ``access_point`` wireless lan + """ + + backend = RadioAirOs + + def test_active_radio(self): o = self.backend({ + "interfaces": [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'radio': 'ath0', + 'mode': 'access_point', + 'bssid': '00:11:22:33:44:55', + 'ssid': 'ubnt', + 'encryption': { + 'protocol': 'none', + }, + }, + }, + ], "radios": [ { 'name': 'ath0', - 'channel': 1, + 'channel': 36, 'channel_width': 20, - 'disabled': True, - 'protocol': '802.11n', + 'protocol': '802.11ac', } ] }) o.to_intermediate() expected = [ { - '1.chanbw': 20, + '1.ack.auto': 'enabled', + '1.ackdistance': 643, + '1.acktimeout': 35, + '1.ampdu.frames': 32, + '1.ampdu.status': 'enabled', + '1.antenna.gain': 3, + '1.antenna.id': 2, + '1.atpc.sta.status': 'enabled', + '1.atpc.status': 'disabled', + '1.atpc.threshold': 36, + '1.cable.loss': 0, + '1.center.1.freq': 0, + '1.chanbw': 80, + '1.cmsbias': 0, + '1.countrycode': 380, + '1.cwm.enable': 0, + '1.cwm.mode': 1, '1.devname': 'ath0', - '1.status': 'disabled', - '1.txpower': '', + '1.dfs.status': 'enabled', + '1.freq': 0, + '1.ieee_mode': '11acvht80', + '1.low_txpower_mode': 'disabled', + '1.mode': 'master', + '1.obey': 'enabled', + '1.polling': 'enabled', + '1.polling_11ac_11n_compat': 0, + '1.polling_ff_dl_ratio': 50, + '1.polling_ff_dur': 0, + '1.polling_ff_timing': 0, + '1.pollingnoack': 0, + '1.pollingpri': 2, + '1.ptpmode': 1, + '1.reg_obey': 'enabled', + '1.rx_sensitivity': -96, + '1.scan_list.status': 'disabled', + '1.scanbw.status': 'disabled', + '1.status': 'enabled', + '1.subsystemid': 0xe7f5, + '1.txpower': 24, }, { + 'countrycode': 380, 'status': 'enabled', + }, ] From a36a1a71247108870ead81e13d369a854e40b81d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 3 Aug 2017 11:45:14 +0200 Subject: [PATCH 253/342] [airos] moved wireless user configuration to different module --- netjsonconfig/backends/airos/converters.py | 28 +-------- netjsonconfig/backends/airos/wireless.py | 69 ++++++++++++++++++++++ 2 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 netjsonconfig/backends/airos/wireless.py diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index bfd97fdf4..516f92199 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -8,6 +8,7 @@ from .radio import radio_available_mode, radio_configuration from .radius import radius_from_interface from .schema import default_ntp_servers +from .wireless import wireless_available_mode from .wpasupplicant import available_mode_authentication @@ -650,31 +651,8 @@ def to_intermediate(self): result = [] wireless_list = [] for w in self.wireless: - wireless_list.append({ - 'addmtikie': 'enabled', - 'devname': radio(w), - 'hide_ssid': hidden_ssid(w), - 'l2_isolation': 'disabled', - 'mac_acl': { - 'policy': 'allow', - 'status': 'disabled', - }, - 'mcast': {'enhance': 0}, - 'rate': { - 'auto': 'enabled', - 'mcs': -1, - }, - 'security': {'type': 'none'}, - 'signal_led1': 75, - 'signal_led2': 50, - 'signal_led3': 25, - 'signal_led4': 15, - 'signal_led_status': 'enabled', - 'ssid': ssid(w), - 'ap': bssid(w), - 'status': status(w), - 'wds': {'status': 'enabled'}, - }) + user_config = wireless_available_mode[mode(w)](w) + wireless_list.append(user_config) result.append(wireless_list) result.append({'status': 'enabled'}) return (('wireless', result),) diff --git a/netjsonconfig/backends/airos/wireless.py b/netjsonconfig/backends/airos/wireless.py new file mode 100644 index 000000000..5c42524b9 --- /dev/null +++ b/netjsonconfig/backends/airos/wireless.py @@ -0,0 +1,69 @@ +from .interface import bssid, hidden_ssid, radio, ssid + +_wireless_base = { + 'addmtikie': 'enabled', + 'devname': '', + 'hide_ssid': '', + 'l2_isolation': 'disabled', + 'mac_acl': { + 'policy': 'allow', + 'status': 'disabled', + }, + 'mcast': {'enhance': 0}, + 'rate': { + 'auto': 'enabled', + 'mcs': -1, + }, + 'security': {'type': 'none'}, + 'signal_led1': 75, + 'signal_led2': 50, + 'signal_led3': 25, + 'signal_led4': 15, + 'signal_led_status': 'enabled', + 'ssid': '', + 'ap': '', + 'status': '', + 'wds': {'status': 'enabled'}, +} + + +def status(interface): + if interface.get('disabled'): + return 'disabled' + else: + return 'enabled' + + +def access_point(wlan): + """ + Return the configuration for a wireless lan in ``access_point`` mode + """ + base = _wireless_base.copy() + base.update({ + 'devname': radio(wlan), + 'hide_ssid': hidden_ssid(wlan), + 'ssid': ssid(wlan), + 'status': status(wlan), + }) + return base + + +def station(wlan): + """ + Return the configuration for a wireless lan in ``station`` mode + """ + base = _wireless_base.copy() + base.update({ + 'ap': bssid(wlan), + 'devname': radio(wlan), + 'hide_ssid': hidden_ssid(wlan), + 'ssid': ssid(wlan), + 'status': status(wlan), + }) + return base + + +wireless_available_mode = { + 'access_point': access_point, + 'station': station, +} From 7ad900b39675e89dc43ca754ece8f4c2121d41c0 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 3 Aug 2017 14:05:05 +0200 Subject: [PATCH 254/342] [airos] added channel width support for radio interfaces, updated tests --- netjsonconfig/backends/airos/radio.py | 24 ++++++-- tests/airos/test_radio.py | 87 ++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 7 deletions(-) diff --git a/netjsonconfig/backends/airos/radio.py b/netjsonconfig/backends/airos/radio.py index 1d2613aa2..33fb1f75b 100644 --- a/netjsonconfig/backends/airos/radio.py +++ b/netjsonconfig/backends/airos/radio.py @@ -54,16 +54,30 @@ } +def channel_to_mode(channel): + """ + Returns the ``ieee_mode`` value from the channel width + """ + mapping = { + 10: '11acvht20', + 20: '11acvht20', + 40: '11acvht40', + 60: '11acvht40', + 80: '11acvht80', + } + return mapping[channel] + + def access_point(radio): """ Return the configuration for a radio device whose wireless interface is in ``access_point`` mode """ - base = _radio_device_base.copy() + base = radio_device_base.copy() base.update({ 'devname': radio['name'], - 'chanbw': 80, - 'ieee_mode': '11acvht80', + 'chanbw': radio['channel_width'], + 'ieee_mode': channel_to_mode(radio['channel_width']), 'mode': 'master', }) return base @@ -74,9 +88,10 @@ def station(radio): Return the configuration for a radio device whose wireless interface is in ``station`` mode """ - base = _radio_device_base.copy() + base = radio_device_base.copy() base.update({ 'devname': radio['name'], + 'chanbw': radio['channel_width'], 'txpower': radio.get('tx_power', 24), }) return base @@ -87,6 +102,7 @@ def station(radio): 'station': station, } + __all__ = [ radio_available_mode, radio_configuration, diff --git a/tests/airos/test_radio.py b/tests/airos/test_radio.py index fdcc8c703..5330af318 100644 --- a/tests/airos/test_radio.py +++ b/tests/airos/test_radio.py @@ -63,7 +63,88 @@ def test_active_radio(self): '1.atpc.threshold': 36, '1.cable.loss': 0, '1.center.1.freq': 0, - '1.chanbw': 0, + '1.chanbw': 20, + '1.cmsbias': 0, + '1.countrycode': 380, + '1.cwm.enable': 0, + '1.cwm.mode': 1, + '1.devname': 'ath0', + '1.dfs.status': 'enabled', + '1.freq': 0, + '1.ieee_mode': 'auto', + '1.low_txpower_mode': 'disabled', + '1.mode': 'managed', + '1.obey': 'enabled', + '1.polling': 'enabled', + '1.polling_11ac_11n_compat': 0, + '1.polling_ff_dl_ratio': 50, + '1.polling_ff_dur': 0, + '1.polling_ff_timing': 0, + '1.pollingnoack': 0, + '1.pollingpri': 2, + '1.ptpmode': 1, + '1.reg_obey': 'enabled', + '1.rx_sensitivity': -96, + '1.scan_list.status': 'disabled', + '1.scanbw.status': 'disabled', + '1.status': 'enabled', + '1.subsystemid': 0xe7f5, + '1.txpower': 24, + }, + { + 'countrycode': 380, + 'status': 'enabled', + } + ] + + self.assertEqualConfig(o.intermediate_data['radio'], expected) + + + def test_channel_width(self): + """ + TODO: channel brandwidth tested only on 802.11ac + """ + o = self.backend({ + "interfaces": [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'radio': 'ath0', + 'mode': 'station', + 'ssid': 'ubnt', + 'bssid': '00:11:22:33:44:55', + 'encryption': { + 'protocol': 'none', + }, + }, + }, + ], + "radios": [ + { + 'name': 'ath0', + 'channel': 1, + 'channel_width': 80, + 'protocol': '802.11ac', + } + ] + }) + o.to_intermediate() + expected = [ + { + '1.ack.auto': 'enabled', + '1.ackdistance': 643, + '1.acktimeout': 35, + '1.ampdu.frames': 32, + '1.ampdu.status': 'enabled', + '1.antenna.gain': 3, + '1.antenna.id': 2, + '1.atpc.sta.status': 'enabled', + '1.atpc.status': 'disabled', + '1.atpc.threshold': 36, + '1.cable.loss': 0, + '1.center.1.freq': 0, + '1.chanbw': 80, '1.cmsbias': 0, '1.countrycode': 380, '1.cwm.enable': 0, @@ -148,7 +229,7 @@ def test_active_radio(self): '1.atpc.threshold': 36, '1.cable.loss': 0, '1.center.1.freq': 0, - '1.chanbw': 80, + '1.chanbw': 20, '1.cmsbias': 0, '1.countrycode': 380, '1.cwm.enable': 0, @@ -156,7 +237,7 @@ def test_active_radio(self): '1.devname': 'ath0', '1.dfs.status': 'enabled', '1.freq': 0, - '1.ieee_mode': '11acvht80', + '1.ieee_mode': '11acvht20', '1.low_txpower_mode': 'disabled', '1.mode': 'master', '1.obey': 'enabled', From 48ab7f014bb3c2f61fc2e84c12c133b7d198fc91 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 3 Aug 2017 16:50:37 +0200 Subject: [PATCH 255/342] [airos] make flake happier --- netjsonconfig/backends/airos/converters.py | 3 ++- tests/airos/test_radio.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 516f92199..8c6b3d830 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,7 +4,8 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface -from .interface import autonegotiation, bridge, bssid, flowcontrol, hidden_ssid, mode, protocol, radio, split_cidr, ssid, stp, vlan, wireless +from .interface import (autonegotiation, bridge, flowcontrol, mode, protocol, + radio, split_cidr, stp, vlan, wireless) from .radio import radio_available_mode, radio_configuration from .radius import radius_from_interface from .schema import default_ntp_servers diff --git a/tests/airos/test_radio.py b/tests/airos/test_radio.py index 5330af318..fcbe34e3c 100644 --- a/tests/airos/test_radio.py +++ b/tests/airos/test_radio.py @@ -99,7 +99,6 @@ def test_active_radio(self): self.assertEqualConfig(o.intermediate_data['radio'], expected) - def test_channel_width(self): """ TODO: channel brandwidth tested only on 802.11ac From e86f74e8e41a195692afb43b70e9f84c2594e144 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 10:50:00 +0200 Subject: [PATCH 256/342] [airos] fixed typo in encrpytion schema for access point --- netjsonconfig/backends/airos/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 4419f920b..350ee922c 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -49,7 +49,7 @@ "oneOf": [ {"$ref": "#/definitions/encryption_none"}, {"$ref": "#/definitions/encryption_wpa_personal"}, - {"$ref": "#/definitions/encryption_wpa_enterprise_sta"}, + {"$ref": "#/definitions/encryption_wpa_enterprise_ap"}, ], }, }, From 4bb886803f8db591d188a921036cbf60deb7f3d4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 11:16:50 +0200 Subject: [PATCH 257/342] [airos] enable wpa2_enterprise on station with tests --- netjsonconfig/backends/airos/wpasupplicant.py | 1 + tests/airos/test_wpasupplicant.py | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index 4e738bdab..ff4cee5e2 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -132,5 +132,6 @@ def sta_wpa2_enterprise(interface): 'station': { 'none': sta_no_encryption, 'wpa2_personal': sta_wpa2_personal, + 'wpa2_enterprise': sta_wpa2_enterprise, }, } diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index d6a597b53..81f93fe63 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -122,7 +122,6 @@ def test_wpa2_personal(self): ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) - @skip("target wpa2_enterprise later") def test_wpa2_enterprise(self): o = self.backend({ "interfaces": [ @@ -132,14 +131,16 @@ def test_wpa2_enterprise(self): "mac": "de:9f:db:30:c9:c5", "mtu": 1500, "txqueuelen": 1000, - "autostart": True, "wireless": { "radio": "radio0", "mode": "station", "ssid": "ap-ssid-example", + "bssid": "00:11:22:33:44:55", "encryption": { "protocol": "wpa2_enterprise", - "key": "cucumber", + "eap_type": "tls", + "identity": "definitely-fake-identity", + "password": "password1234", }, }, } @@ -147,9 +148,6 @@ def test_wpa2_enterprise(self): }) o.to_intermediate() expected = [ - { - 'status': 'enabled', - }, { 'device.1.profile': 'AUTO', 'device.1.status': 'enabled', @@ -162,7 +160,6 @@ def test_wpa2_enterprise(self): 'profile.1.network.1.password': 'TODO', 'profile.1.network.1.identity': 'TODO', 'profile.1.network.1.anonymous_identity': 'TODO', - 'profile.1.network.1.psk': 'cucumber', 'profile.1.network.1.pairwise.1.name': 'CCMP', 'profile.1.network.1.proto.1.name': 'RSN', 'profile.1.network.1.ssid': 'ap-ssid-example', @@ -172,6 +169,9 @@ def test_wpa2_enterprise(self): 'profile.1.network.2.priority': 2, 'profile.1.network.2.status': 'disabled', }, + { + 'status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) From a60194cf9611f6025252c971824d14ab765ab64d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 11:21:01 +0200 Subject: [PATCH 258/342] [airos] use ssid function from interface module --- netjsonconfig/backends/airos/wpasupplicant.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index ff4cee5e2..85663a732 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -1,10 +1,12 @@ +from interface import ssid + def ap_no_encryption(interface): """ Returns the wpasupplicant.profile.1.network for encryption None as the intermediate dict """ return { - 'ssid': interface['wireless']['ssid'], + 'ssid': ssid(interface), 'priority': 100, 'key_mgmt': [ { @@ -22,7 +24,7 @@ def ap_wpa2_personal(interface): """ return { 'psk': interface['wireless']['encryption']['key'], - 'ssid': interface['wireless']['ssid'], + 'ssid': ssid(interface), 'key_mgmt': [ { 'name': 'NONE', @@ -39,7 +41,7 @@ def sta_no_encryption(interface): in ``station`` mode """ return { - 'ssid': interface['wireless']['ssid'], + 'ssid': ssid(interface), 'priority': 100, 'key_mgmt': [ { @@ -56,7 +58,7 @@ def sta_wpa2_personal(interface): in ``station`` mode """ return { - 'ssid': interface['wireless']['ssid'], + 'ssid': ssid(interface), 'psk': interface['wireless']['encryption']['key'], # no advanced authentication methods # with psk @@ -94,7 +96,7 @@ def sta_wpa2_enterprise(interface): for wpa2_enterprise as the intermediate dict """ return { - 'ssid': interface['wireless']['ssid'], + 'ssid': ssid(interface), 'phase2=auth': 'MSCHAPV2', 'eap': [ { From c47075947a3ea8caebbc0ba91256a98514a789cf Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 11:33:21 +0200 Subject: [PATCH 259/342] [airos] add password and identity to wpa2_enterprise converter --- netjsonconfig/backends/airos/wpasupplicant.py | 11 ++++++----- tests/airos/test_wpasupplicant.py | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index 85663a732..1394f1709 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -1,4 +1,5 @@ -from interface import ssid +from interface import encryption, ssid + def ap_no_encryption(interface): """ @@ -23,7 +24,7 @@ def ap_wpa2_personal(interface): in ``access_point`` mode """ return { - 'psk': interface['wireless']['encryption']['key'], + 'psk': encryption(interface)['key'], 'ssid': ssid(interface), 'key_mgmt': [ { @@ -59,7 +60,7 @@ def sta_wpa2_personal(interface): """ return { 'ssid': ssid(interface), - 'psk': interface['wireless']['encryption']['key'], + 'psk': encryption(interface)['key'], # no advanced authentication methods # with psk 'eap': [ @@ -104,8 +105,8 @@ def sta_wpa2_enterprise(interface): 'status': 'enabled', }, ], - 'password': 'TODO', - 'identity': 'TODO', + 'password': encryption(interface)['password'], + 'identity': encryption(interface)['identity'], 'anonymous_identity': 'TODO', 'pairwise': [ { diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 81f93fe63..afa651ba6 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -157,8 +157,8 @@ def test_wpa2_enterprise(self): 'profile.1.network.1.phase2=auth': 'MSCHAPV2', 'profile.1.network.1.eap.1.status': 'enabled', 'profile.1.network.1.eap.1.name': 'TTLS', - 'profile.1.network.1.password': 'TODO', - 'profile.1.network.1.identity': 'TODO', + 'profile.1.network.1.password': 'password1234', + 'profile.1.network.1.identity': 'definitely-fake-identity', 'profile.1.network.1.anonymous_identity': 'TODO', 'profile.1.network.1.pairwise.1.name': 'CCMP', 'profile.1.network.1.proto.1.name': 'RSN', From b5ee061c8ce0c4ed6f1865fc28d3d70fdc2a10af Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 11:36:44 +0200 Subject: [PATCH 260/342] [airos] added encryption function to interface module --- netjsonconfig/backends/airos/interface.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index 4890c511e..6fc24d518 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -25,6 +25,13 @@ def bssid(interface): return interface['wireless'].get('bssid', '') +def encryption(interface): + """ + Return the encryption dict for a wireless interface + """ + return interface['wireless']['encryption'] + + def flowcontrol(interface): """ Return the configuration for ``flowcontrol`` on interface From 24a79ffa2254aa7b134fa6d26f9198819ebe8604 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:32:40 +0200 Subject: [PATCH 261/342] [airos] sort keys in expected output --- tests/airos/test_wpasupplicant.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index afa651ba6..123dc761a 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -149,18 +149,19 @@ def test_wpa2_enterprise(self): o.to_intermediate() expected = [ { + 'device.1.devname': 'radio0', + 'device.1.driver': 'madwifi', 'device.1.profile': 'AUTO', 'device.1.status': 'enabled', - 'device.1.driver': 'madwifi', - 'device.1.devname': 'radio0', 'profile.1.name': 'AUTO', - 'profile.1.network.1.phase2=auth': 'MSCHAPV2', - 'profile.1.network.1.eap.1.status': 'enabled', 'profile.1.network.1.eap.1.name': 'TTLS', - 'profile.1.network.1.password': 'password1234', + 'profile.1.network.1.eap.1.status': 'enabled', 'profile.1.network.1.identity': 'definitely-fake-identity', - 'profile.1.network.1.anonymous_identity': 'TODO', + 'profile.1.network.1.key_mgmt.1.name': 'WPA-EAP', 'profile.1.network.1.pairwise.1.name': 'CCMP', + 'profile.1.network.1.password': 'password1234', + 'profile.1.network.1.phase2=auth': 'MSCHAPV2', + 'profile.1.network.1.priority': 100, 'profile.1.network.1.proto.1.name': 'RSN', 'profile.1.network.1.ssid': 'ap-ssid-example', 'profile.1.network.1.priority': 100, From 5dc4384f0af0e195b726d6201d4d5c5d495e3d1e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:33:21 +0200 Subject: [PATCH 262/342] [airos] add support for wpa2_enterprise aaa generation --- netjsonconfig/backends/airos/aaa.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/netjsonconfig/backends/airos/aaa.py b/netjsonconfig/backends/airos/aaa.py index 33f96b472..115294d6f 100644 --- a/netjsonconfig/backends/airos/aaa.py +++ b/netjsonconfig/backends/airos/aaa.py @@ -24,6 +24,10 @@ def ap_psk(interface): return result +def ap_eap(interface): + return {} + + def sta_none(interface): return {} @@ -36,16 +40,22 @@ def sta_psk(interface): } +def sta_eap(interface): + return {} + + _profile = {} _profile_from_mode = { 'access_point': { 'none': ap_none, 'wpa2_personal': ap_psk, + 'wpa2_enterprise': ap_eap, }, 'station': { 'none': sta_none, 'wpa2_personal': sta_psk, + 'wpa2_enterprise': sta_eap, }, } @@ -68,6 +78,9 @@ def profile_from_interface(interface): 'wpa2_personal': { 'status': 'enabled', }, + 'wpa2_enterprise': { + 'status': '', + }, }, 'station': { 'none': { @@ -76,6 +89,9 @@ def profile_from_interface(interface): 'wpa2_personal': { 'status': 'disabled', }, + 'wpa2_enterprise': { + 'status': '', + }, } } From 3f2ffaa76262d126695f35bf8ea9eca364bf43df Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:33:54 +0200 Subject: [PATCH 263/342] [airos] added support for generating radius configuration in wpa2_enterprise --- netjsonconfig/backends/airos/radius.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netjsonconfig/backends/airos/radius.py b/netjsonconfig/backends/airos/radius.py index ca9d323bd..91386c236 100644 --- a/netjsonconfig/backends/airos/radius.py +++ b/netjsonconfig/backends/airos/radius.py @@ -38,10 +38,12 @@ }, 'status': 'enabled', }, + 'wpa2_enterprise': {}, }, 'station': { 'none': {}, 'wpa2_personal': {}, + 'wpa2_enterprise': {}, } } From ca0cbf31fb7a0f24bfaf4cb48007a656654d2b36 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:34:46 +0200 Subject: [PATCH 264/342] [airos] removed anonymous identity from station wpa2_enterprise wpasupplicant function --- netjsonconfig/backends/airos/wpasupplicant.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index 1394f1709..7cba70e97 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -107,7 +107,6 @@ def sta_wpa2_enterprise(interface): ], 'password': encryption(interface)['password'], 'identity': encryption(interface)['identity'], - 'anonymous_identity': 'TODO', 'pairwise': [ { 'name': 'CCMP', From a810d7598c0338f6d8392f6f54b35a9ffccd9078 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:37:01 +0200 Subject: [PATCH 265/342] [airos] add test for wpa2_enterprise peap method in station mode --- tests/airos/test_wpasupplicant.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 123dc761a..5dbc0be7a 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -164,8 +164,34 @@ def test_wpa2_enterprise(self): 'profile.1.network.1.priority': 100, 'profile.1.network.1.proto.1.name': 'RSN', 'profile.1.network.1.ssid': 'ap-ssid-example', - 'profile.1.network.1.priority': 100, + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) + + @skip("target later") + def test_peap_wpa2_enterprise(self): + expected = [ + { + 'device.1.devname': 'radio0', + 'device.1.driver': 'madwifi', + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'profile.1.network.1.eap.1.name': 'PEAP', + 'profile.1.network.1.eap.1.status': 'enabled', + 'profile.1.network.1.identity': 'definitely-fake-identity', 'profile.1.network.1.key_mgmt.1.name': 'WPA-EAP', + 'profile.1.network.1.pairwise.1.name': 'CCMP', + 'profile.1.network.1.password': 'password1234', + 'profile.1.network.1.phase2=auth': 'MSCHAPV2', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.proto.1.name': 'RSN', + 'profile.1.network.1.ssid': 'ap-ssid-example', 'profile.1.network.2.key_mgmt.1.name': 'NONE', 'profile.1.network.2.priority': 2, 'profile.1.network.2.status': 'disabled', From 8fd8a429e3f2f68cc7fd0c67475c33c09f3b62c9 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:38:15 +0200 Subject: [PATCH 266/342] [airos] added test for eap method in wpa2_enterprise for access point --- tests/airos/test_wpasupplicant.py | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 5dbc0be7a..8d32edd0b 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -290,3 +290,55 @@ def test_wpa2_personal(self): } ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) + + def test_eap_wpa2_enterprise(self): + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "wireless": { + "radio": "radio0", + "mode": "access_point", + "ssid": "ap-ssid-example", + "encryption": { + "protocol": "wpa2_enterprise", + "server": "radius.example.com", + "key": "the-shared-key", + "acct_server": "accounting.example.com", + }, + }, + } + ] + }) + o.to_intermediate() + expected = [ + { + 'device.1.devname': 'radio0', + 'device.1.driver': 'madwifi', + 'device.1.profile': 'AUTO', + 'device.1.status': 'enabled', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.anonymous_identity': 'TODO', + 'profile.1.network.1.eap.1.name': 'TTLS', + 'profile.1.network.1.eap.1.status': 'enabled', + 'profile.1.network.1.identity': 'TODO', + 'profile.1.network.1.key_mgmt.1.name': 'WPA-EAP', + 'profile.1.network.1.pairwise.1.name': 'CCMP', + 'profile.1.network.1.password': 'TODO', + 'profile.1.network.1.phase2=auth': 'MSCHAPV2', + 'profile.1.network.1.priority': 100, + 'profile.1.network.1.proto.1.name': 'RSN', + 'profile.1.network.1.ssid': 'ap-ssid-example', + 'profile.1.network.2.key_mgmt.1.name': 'NONE', + 'profile.1.network.2.priority': 2, + 'profile.1.network.2.status': 'disabled', + }, + { + 'status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) From 5e4e3dbaa21beb712d4f5278cbff363d90755a7b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:39:38 +0200 Subject: [PATCH 267/342] [airos] added support for wpa2_enterprise in wpasupplicant converter --- netjsonconfig/backends/airos/converters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 8c6b3d830..ec98c75de 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -711,6 +711,7 @@ def _access_point_intermediate(self, original): } wpasupplicant_status = { 'wpa2_personal': 'disabled', + 'wpa2_enterprise': 'disabled', 'none': 'enabled', } result = [] From 38612f48cce232afd495185601c1d8dca0db5c26 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:40:34 +0200 Subject: [PATCH 268/342] [airos] fixed name conflict in wpasupplicant converter --- netjsonconfig/backends/airos/converters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index ec98c75de..45141a735 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -718,13 +718,13 @@ def _access_point_intermediate(self, original): if original: head = original[0] - protocol = head['wireless']['encryption']['protocol'] - status = wpasupplicant_status[protocol] + proto = protocol(head) + status = wpasupplicant_status[proto] result.append({ 'status': status }) temp_dev['status'] = status - network = ap_auth_protocols[protocol](head) + network = ap_auth_protocols[proto](head) result.append({ 'device': [temp_dev], From cd86dc80f0621ecb5b479e0966920f0f60cc9e16 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:41:15 +0200 Subject: [PATCH 269/342] [airos] added support for access point in wpa2_enterprise mode to module --- netjsonconfig/backends/airos/wpasupplicant.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index 7cba70e97..e4044e30d 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -35,6 +35,41 @@ def ap_wpa2_personal(interface): } +def ap_wpa2_enterprise(interface): + """ + Returns the wpasupplicant.profile.1.network + for wpa2_personal as the indernediate dict + in ``access_point`` mode + """ + return { + 'ssid': ssid(interface), + 'phase2=auth': 'MSCHAPV2', + 'eap': [ + { + 'name': 'TTLS', + 'status': 'enabled', + }, + ], + 'anonymous_identity': 'TODO', + 'pairwise': [ + { + 'name': 'CCMP', + }, + ], + 'proto': [ + { + 'name': 'RSN', + }, + ], + 'priority': 100, + 'key_mgmt': [ + { + 'name': 'WPA-EAP', + }, + ], + } + + def sta_no_encryption(interface): """ Returns the wpasupplicant.profile.1.network @@ -130,6 +165,7 @@ def sta_wpa2_enterprise(interface): 'access_point': { 'none': ap_no_encryption, 'wpa2_personal': ap_wpa2_personal, + 'wpa2_enterprise': ap_wpa2_enterprise, }, 'station': { 'none': sta_no_encryption, From 100730e8ab93ab576839fdcaebeaa4b0eb6a7deb Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 12:42:42 +0200 Subject: [PATCH 270/342] [airos] drafted configuration input for peap in wpa2_enterprise in test --- tests/airos/test_wpasupplicant.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 8d32edd0b..99484d17d 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -176,6 +176,30 @@ def test_wpa2_enterprise(self): @skip("target later") def test_peap_wpa2_enterprise(self): + + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "mac": "de:9f:db:30:c9:c5", + "mtu": 1500, + "txqueuelen": 1000, + "wireless": { + "radio": "radio0", + "mode": "station", + "ssid": "ap-ssid-example", + "encryption": { + "protocol": "wpa2_enterprise", + "server": "radius.example.com", + "key": "the-shared-key", + "acct_server": "accounting.example.com", + }, + }, + } + ] + }) + o.to_intermediate() expected = [ { 'device.1.devname': 'radio0', From c2aadb439063a0ff9386528ca5400b40f190725e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 15:29:00 +0200 Subject: [PATCH 271/342] [airos] updated converter and wpasupplicant module to support wpa2_enterprise --- netjsonconfig/backends/airos/converters.py | 17 +++++++----- netjsonconfig/backends/airos/wpasupplicant.py | 27 ++----------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 45141a735..f75bacbe8 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -710,9 +710,9 @@ def _access_point_intermediate(self, original): 'profile': 'AUTO', } wpasupplicant_status = { + 'none': 'enabled', 'wpa2_personal': 'disabled', 'wpa2_enterprise': 'disabled', - 'none': 'enabled', } result = [] @@ -725,15 +725,18 @@ def _access_point_intermediate(self, original): }) temp_dev['status'] = status network = ap_auth_protocols[proto](head) + profile = { + 'name': 'AUTO', + 'network': [network, self.secondary_network()], + } + + if proto == 'wpa2_enterprise': + del temp_dev['profile'] + del profile['name'] result.append({ 'device': [temp_dev], - 'profile': [ - { - 'name': 'AUTO', - 'network': [network, self.secondary_network()], - }, - ], + 'profile': [profile], }) return (('wpasupplicant', result),) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index e4044e30d..9b783ae1f 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -26,12 +26,12 @@ def ap_wpa2_personal(interface): return { 'psk': encryption(interface)['key'], 'ssid': ssid(interface), + 'priority': 100, 'key_mgmt': [ { 'name': 'NONE', }, ], - 'priority': 100, } @@ -43,33 +43,10 @@ def ap_wpa2_enterprise(interface): """ return { 'ssid': ssid(interface), - 'phase2=auth': 'MSCHAPV2', - 'eap': [ - { - 'name': 'TTLS', - 'status': 'enabled', - }, - ], - 'anonymous_identity': 'TODO', - 'pairwise': [ - { - 'name': 'CCMP', - }, - ], - 'proto': [ - { - 'name': 'RSN', - }, - ], - 'priority': 100, - 'key_mgmt': [ - { - 'name': 'WPA-EAP', - }, - ], } +# STATION def sta_no_encryption(interface): """ Returns the wpasupplicant.profile.1.network From a573c835c8fb2554b68aec9f262ffac1f8921429 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 8 Aug 2017 15:31:53 +0200 Subject: [PATCH 272/342] [airos] updated expected result for wpa2_enterprise in access point --- tests/airos/test_wpasupplicant.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 99484d17d..ff86f16b5 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -341,28 +341,14 @@ def test_eap_wpa2_enterprise(self): o.to_intermediate() expected = [ { - 'device.1.devname': 'radio0', - 'device.1.driver': 'madwifi', - 'device.1.profile': 'AUTO', - 'device.1.status': 'enabled', - 'profile.1.name': 'AUTO', - 'profile.1.network.1.anonymous_identity': 'TODO', - 'profile.1.network.1.eap.1.name': 'TTLS', - 'profile.1.network.1.eap.1.status': 'enabled', - 'profile.1.network.1.identity': 'TODO', - 'profile.1.network.1.key_mgmt.1.name': 'WPA-EAP', - 'profile.1.network.1.pairwise.1.name': 'CCMP', - 'profile.1.network.1.password': 'TODO', - 'profile.1.network.1.phase2=auth': 'MSCHAPV2', - 'profile.1.network.1.priority': 100, - 'profile.1.network.1.proto.1.name': 'RSN', + 'status': 'disabled', + }, + { + 'device.1.status': 'disabled', 'profile.1.network.1.ssid': 'ap-ssid-example', 'profile.1.network.2.key_mgmt.1.name': 'NONE', 'profile.1.network.2.priority': 2, 'profile.1.network.2.status': 'disabled', }, - { - 'status': 'enabled', - }, ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) From 717fcb00a0d26ecb348db90ef6336d42ea4b1d6c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 9 Aug 2017 18:48:58 +0200 Subject: [PATCH 273/342] [airos] updated aaa section configuration many functions are now splitted for the interface mode and can be found in the radius module or the aaa module --- netjsonconfig/backends/airos/aaa.py | 55 ++++++-- netjsonconfig/backends/airos/converters.py | 5 - netjsonconfig/backends/airos/radius.py | 146 ++++++++++++++------- 3 files changed, 143 insertions(+), 63 deletions(-) diff --git a/netjsonconfig/backends/airos/aaa.py b/netjsonconfig/backends/airos/aaa.py index 115294d6f..d736b435d 100644 --- a/netjsonconfig/backends/airos/aaa.py +++ b/netjsonconfig/backends/airos/aaa.py @@ -2,21 +2,25 @@ def ap_none(interface): + """ + Returns the configuration for ``aaa`` + when in ``access_point`` mode without authentication + """ return {} def ap_psk(interface): + """ + Returns the configuration for ``aaa`` + when in ``access_point`` mode with psk authentication + """ result = { 'devname': radio(interface), 'driver': 'madwifi', 'ssid': ssid(interface), 'wpa': { '1.pairwise': 'CCMP', - 'key': [ - { - 'mgmt': 'WPA-PSK', - } - ], + 'key': [{'mgmt': 'WPA-PSK'}], 'mode': 2, 'psk': psk(interface), } @@ -25,14 +29,35 @@ def ap_psk(interface): def ap_eap(interface): - return {} + """ + Return the configuration for ``aaa`` + when in ``access_point`` mode with eap authentication + """ + return { + 'devname': radio(interface), + 'driver': 'madwifi', + 'ssid': ssid(interface), + 'wpa': { + '1.pairwise': 'CCMP', + 'key': [{'mgmt': 'WPA-EAP'}], + 'mode': 2, + }, + } def sta_none(interface): + """ + Return the configuration for ``aaa`` + when in station mode without authentication + """ return {} def sta_psk(interface): + """ + Return the configuration for ``aaa`` + when in station mode with psk authentication + """ return { 'wpa': { 'psk': psk(interface), @@ -41,6 +66,10 @@ def sta_psk(interface): def sta_eap(interface): + """ + Return the configuration for ``aaa`` + when in station mode with eap authentication + """ return {} @@ -61,6 +90,9 @@ def sta_eap(interface): def profile_from_interface(interface): + """ + Returns the ``aaa`` configuration for interface + """ profile = _profile.copy() profile.update( _profile_from_mode[mode(interface)][protocol(interface)](interface) @@ -79,7 +111,7 @@ def profile_from_interface(interface): 'status': 'enabled', }, 'wpa2_enterprise': { - 'status': '', + 'status': 'enabled', }, }, 'station': { @@ -90,13 +122,16 @@ def profile_from_interface(interface): 'status': 'disabled', }, 'wpa2_enterprise': { - 'status': '', + 'status': 'disabled', }, } } def status_from_interface(interface): + """ + Returns ``aaa.status`` from interface + """ status = _status.copy() status.update( _status_from_mode[mode(interface)][protocol(interface)] @@ -106,12 +141,12 @@ def status_from_interface(interface): def bridge_devname(wireless_interface, bridge_interface): """ - when in ``access_point`` with ``wpa2_personal`` authentication set also the + when in ``access_point`` with authentication set also the bridge interface name TODO: check if in ``netmode=router`` this happens again """ - if mode(wireless_interface) == 'access_point' and protocol(wireless_interface) == 'wpa2_personal': + if mode(wireless_interface) == 'access_point' and protocol(wireless_interface) != 'none': return { 'br': { 'devname': bridge_interface['name'], diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index f75bacbe8..369a6cca6 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -51,11 +51,6 @@ def wireless(self): """ return wireless(get_copy(self.netjson, 'interfaces', [])) - @property - def radius(self): - original = get_copy(self.netjson, 'radius', {}) - return original - def to_intermediate(self): base = {} result = [] diff --git a/netjsonconfig/backends/airos/radius.py b/netjsonconfig/backends/airos/radius.py index 91386c236..d4bd9dc19 100644 --- a/netjsonconfig/backends/airos/radius.py +++ b/netjsonconfig/backends/airos/radius.py @@ -1,59 +1,109 @@ -from .interface import mode, protocol +from .interface import encryption, mode, protocol -_radius = { - 'radius': { - 'acct': [ - { - 'port': 1813, - 'status': 'disabled', - } - ], - 'auth': [ - { - 'port': 1812, - }, - ], - }, - 'status': 'disabled', +def ap_authentication(interface): + """ + Returns the ``radius.auth`` dict for ``access_point`` interface + """ + result = {} + proto = protocol(interface) + if proto == 'wpa2_personal': + result.update({ + 'status': 'disabled', + }) + elif proto == 'wpa2_enterprise': + enc = encryption(interface) + result.update({ + 'ip': enc.get('server', ''), + 'port': enc.get('port', 1812), + 'secret': enc.get('key',''), + 'status': 'enabled', + }) + return result + + +def sta_authentication(interface): + """ + Returns the ``radius.auth`` dict for ``station`` interface + """ + result = {} + return result + + +_authentication_from_mode = { + 'access_point': ap_authentication, + 'station': sta_authentication, } -_radius_from_mode = { - 'access_point': { - 'none': {}, - 'wpa2_personal': { - 'radius': { - 'auth': [{ - 'port': 1812, - 'status': 'disabled', - }], - 'acct': [ - { - 'port': 1813, - 'status': 'disabled', - } - ], - 'macacl': { - 'status': 'disabled', - }, - }, - 'status': 'enabled', - }, - 'wpa2_enterprise': {}, - }, - 'station': { - 'none': {}, - 'wpa2_personal': {}, - 'wpa2_enterprise': {}, + +def authentication(interface): + """ + returns the ``radius.auth`` dict + """ + result = { + 'port': 1812, } + mod = mode(interface) + result.update(_authentication_from_mode[mode(interface)](interface)) + return result + + +def ap_accounting(interface): + result = {} + if protocol(interface) == 'wpa2_enterprise': + enc = encryption(interface) + result.update({ + 'port': enc.get('acct_server_port', 1813), + 'ip': enc.get('acct_server', ''), + 'status': 'enabled', + }) + return result + +def sta_accounting(interface): + return {} + + +_accounting_from_mode = { + 'access_point': ap_accounting, + 'station': sta_accounting, } +def accounting(interface): + """ + Returns the ``radius.acct`` dict + """ + result = { + 'port': 1813, + 'status': 'disabled', + } + result.update(_accounting_from_mode[mode(interface)](interface)) + return result + + + def radius_from_interface(interface): - radius = _radius.copy() - radius.update( - _radius_from_mode[mode(interface)][protocol(interface)] - ) - return radius + """ + Return the ``radius`` configuration for + section ``aaa`` + """ + result = { + 'radius': { + 'auth': [ + authentication(interface), + ], + 'acct': [ + accounting(interface), + ], + } + } + if protocol(interface) != 'none' and mode(interface) != 'station': + result['radius'].update({ + 'macacl': { + 'status': 'disabled', + }, + }) + + return result __all__ = [ From 5b57351cbba9a4c47eb1fce335ff7db3e28e98b6 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 09:48:47 +0200 Subject: [PATCH 274/342] [airos] fixed default for interface encryption, added docstrings --- netjsonconfig/backends/airos/interface.py | 2 +- netjsonconfig/backends/airos/radius.py | 6 ++++++ netjsonconfig/backends/airos/wireless.py | 3 +++ netjsonconfig/backends/airos/wpasupplicant.py | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index 6fc24d518..232325573 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -29,7 +29,7 @@ def encryption(interface): """ Return the encryption dict for a wireless interface """ - return interface['wireless']['encryption'] + return interface['wireless'].get('encryption', {'protocol': 'none'}) def flowcontrol(interface): diff --git a/netjsonconfig/backends/airos/radius.py b/netjsonconfig/backends/airos/radius.py index d4bd9dc19..4149e0b08 100644 --- a/netjsonconfig/backends/airos/radius.py +++ b/netjsonconfig/backends/airos/radius.py @@ -48,6 +48,9 @@ def authentication(interface): def ap_accounting(interface): + """ + Returns the ``acct`` dict for ``access_point`` interfaces + """ result = {} if protocol(interface) == 'wpa2_enterprise': enc = encryption(interface) @@ -59,6 +62,9 @@ def ap_accounting(interface): return result def sta_accounting(interface): + """ + Returns the ``acct`` dict for ``station`` interfaces + """ return {} diff --git a/netjsonconfig/backends/airos/wireless.py b/netjsonconfig/backends/airos/wireless.py index 5c42524b9..2ab44fec9 100644 --- a/netjsonconfig/backends/airos/wireless.py +++ b/netjsonconfig/backends/airos/wireless.py @@ -28,6 +28,9 @@ def status(interface): + """ + Return the ``wireless`` status + """ if interface.get('disabled'): return 'disabled' else: diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index 9b783ae1f..0f6f37edc 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -1,4 +1,4 @@ -from interface import encryption, ssid +from .interface import encryption, ssid def ap_no_encryption(interface): From bd007b28f99620dce72c762015ca1a18b1200761 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 09:56:48 +0200 Subject: [PATCH 275/342] [airos] make flake happier --- netjsonconfig/backends/airos/radius.py | 6 +- tests/airos/test_aaa.py | 101 ++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/netjsonconfig/backends/airos/radius.py b/netjsonconfig/backends/airos/radius.py index 4149e0b08..8b88f2110 100644 --- a/netjsonconfig/backends/airos/radius.py +++ b/netjsonconfig/backends/airos/radius.py @@ -1,5 +1,6 @@ from .interface import encryption, mode, protocol + def ap_authentication(interface): """ Returns the ``radius.auth`` dict for ``access_point`` interface @@ -15,7 +16,7 @@ def ap_authentication(interface): result.update({ 'ip': enc.get('server', ''), 'port': enc.get('port', 1812), - 'secret': enc.get('key',''), + 'secret': enc.get('key', ''), 'status': 'enabled', }) return result @@ -42,7 +43,6 @@ def authentication(interface): result = { 'port': 1812, } - mod = mode(interface) result.update(_authentication_from_mode[mode(interface)](interface)) return result @@ -61,6 +61,7 @@ def ap_accounting(interface): }) return result + def sta_accounting(interface): """ Returns the ``acct`` dict for ``station`` interfaces @@ -86,7 +87,6 @@ def accounting(interface): return result - def radius_from_interface(interface): """ Return the ``radius`` configuration for diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index 9b7fe938e..9576563b6 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -1,7 +1,7 @@ from .mock import AaaAirOs, ConverterTest -class TestAaaConverter(ConverterTest): +class TestAaaConverterAccess(ConverterTest): backend = AaaAirOs @@ -92,6 +92,64 @@ def test_ap_psk_authentication(self): ] self.assertEqualConfig(o.intermediate_data['aaa'], expected) + def test_ap_eap_authentication(self): + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "wireless": { + "mode": "access_point", + "radio": "ath0", + "ssid": "i-like-pasta", + "encryption": { + "protocol": "wpa2_enterprise", + "key": "secret-radius-key", + "server": "192.168.1.1", + "acct_server": "192.168.1.2", + }, + }, + }, + { + "type": "bridge", + "name": "br0", + "bridge_members": [ + "wlan0", + ], + }, + ], + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + '1.br.devname': 'br0', # only in bridge mode? + '1.devname': 'ath0', + '1.driver': 'madwifi', + '1.radius.acct.1.port': 1813, + '1.radius.acct.1.status': 'enabled', + '1.radius.acct.1.ip': '192.168.1.2', + '1.radius.auth.1.ip': '192.168.1.1', + '1.radius.auth.1.port': 1812, + '1.radius.auth.1.secret': 'secret-radius-key', + '1.radius.auth.1.status': 'enabled', + '1.radius.macacl.status': 'disabled', + '1.ssid': 'i-like-pasta', + '1.status': 'enabled', + '1.wpa.1.pairwise': 'CCMP', + '1.wpa.key.1.mgmt': 'WPA-EAP', + '1.wpa.mode': 2, + } + ] + self.assertEqualConfig(o.intermediate_data['aaa'], expected) + + +class TestAaaConverterStation(ConverterTest): + + backend = AaaAirOs + def test_sta_no_authentication(self): o = self.backend({ "interfaces": [ @@ -171,3 +229,44 @@ def test_sta_psk_authentication(self): }, ] self.assertEqualConfig(o.intermediate_data['aaa'], expected) + + def test_sta_eap_authentication(self): + o = self.backend({ + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "wireless": { + "mode": "station", + "radio": "ath0", + "ssid": "i-like-pasta", + "bssid": "00:11:22:33:44:55", + "encryption": { + "protocol": "wpa2_enterprise", + "identity": "some-fake-identity", + "password": "password1234", + }, + }, + }, + { + "type": "bridge", + "name": "br0", + "bridge_members": [ + "wlan0", + ], + }, + ], + }) + o.to_intermediate() + expected = [ + { + 'status': 'disabled', + }, + { + '1.radius.acct.1.port': 1813, + '1.radius.acct.1.status': 'disabled', + '1.radius.auth.1.port': 1812, + '1.status': 'disabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['aaa'], expected) From 48c81dc136ef256e57e5725b6add8e020d223f94 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 09:59:45 +0200 Subject: [PATCH 276/342] [airos] make isort happier --- netjsonconfig/backends/airos/airos.py | 5 +++-- netjsonconfig/backends/airos/schema.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 7a74e3f8b..caa1a5cef 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -1,10 +1,11 @@ from collections import OrderedDict + from ..base.backend import BaseBackend from .converters import (Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, Httpd, Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, - System, Telnetd, Tshaper, Unms, Update, Users, - Vlan, Wireless, Wpasupplicant) + System, Telnetd, Tshaper, Unms, Update, Users, Vlan, + Wireless, Wpasupplicant) from .intermediate import flatten, intermediate_to_list from .renderers import AirOsRenderer from .schema import schema diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 350ee922c..3e9b27e25 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -4,6 +4,7 @@ from ...schema import schema as default_schema from ...utils import merge_config + """ This defines a new property in the ``Interface``. From 7eb90cd6bf588b7a07d8dd6f171de5c46b02c381 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 10:10:55 +0200 Subject: [PATCH 277/342] [airos] update wpa2_docs, update encryption docs --- docs/source/backends/airos.rst | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 09add5227..3d44dd3dc 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -241,6 +241,9 @@ As an example here is a snippet that set the authentication protocol to WPA2 per "name": "wlan0", "type": "wireless", "wireless": { + "mode": "station", + "radio": "ath0", + "ssid": "ap-ssid-example", "encryption": { "protocol": "wpa2_personal", "key": "changeme" @@ -250,7 +253,7 @@ As an example here is a snippet that set the authentication protocol to WPA2 per ] } -And another that set the authentication protocol to WPA2 enterprise, but this is still not supported by netjsonconfig +And another that set the authentication protocol to WPA2 enterprise .. code-block:: json @@ -260,29 +263,15 @@ And another that set the authentication protocol to WPA2 enterprise, but this is "name": "wlan0", "type": "wireless", "wireless": { + "mode": "station", + "radio": "ath0", + "ssid": "ap-ssid-example", "encryption": { "protocol": "wpa2_enterprise", - "key": "changeme" + "identity": "my-identity", + "password": "changeme", } } } ] - } - -The ``encryption`` property **must** be specified otherwise you will experience a ``ValidationError``, if you are not sure on what you want -use this snippet to set to no encryption - -.. code-block:: json - - { - "interfaces": [ - { - "name": "wlan0", - "type": "wireless", - "wireless": { - "encryption": { - "protocol": "none" - } - } - } - } + From cd3eb2b8fffa9d660a1c40502c9196d90aabb92a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 12:57:44 +0200 Subject: [PATCH 278/342] [airos] add property netmode to converters --- netjsonconfig/backends/airos/converters.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 369a6cca6..7afe6d516 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -29,6 +29,10 @@ class AirOsConverter(BaseConverter): def should_run_forward(cls, config): return True + @property + def netmode(self): + return self.config.get('netmode', 'bridge') + class Aaa(AirOsConverter): netjson_key = 'general' @@ -40,10 +44,6 @@ def bridge(self): """ return bridge(get_copy(self.netjson, 'interfaces', [])) - @property - def netmode(self): - return self.netjson.get('netmode', 'bridge') - @property def wireless(self): """ From 54e7a7942c41fc141334023e5addb60f084e790e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 12:58:19 +0200 Subject: [PATCH 279/342] [airos] updated igmpproxy for router mode --- netjsonconfig/backends/airos/converters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 7afe6d516..ab99a2497 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -199,8 +199,10 @@ class Igmpproxy(AirOsConverter): netjson_key = 'general' def to_intermediate(self): - result = [{'status': 'disabled'}] - return (('igmpproxy', result),) + result = {'status': 'disabled'} + if self.netmode == 'router': + result.update({'upstream': {'devname': ''}}) + return (('igmpproxy', [result]),) class Iptables(AirOsConverter): From d53bff21c3684418c8e7d1ec829e93b9392dd64e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 12:59:02 +0200 Subject: [PATCH 280/342] [airos] added dhcpc converter for router mode --- netjsonconfig/backends/airos/converters.py | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index ab99a2497..8ba41fa15 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -121,6 +121,30 @@ def to_intermediate(self): return (('discovery', result),) +class Dhcp(AirOsConverter): + + @classmethod + def should_run_forward(cls, config): + if config.get('netmode', 'bridge') == 'bridge': + return False + else: + return True + + def to_intermediate(self): + dhcp_interface = { + 'devname': 'br0', + 'fallback': '192.168.10.1', + 'fallback_netmask': '255.255.255.0', + 'status': 'enabled' + } + dchp_status = {'status': 'enabled'} + result = [ + dchp_status, + [dhcp_interface], + ] + return (('dhcpc', result),) + + class Dyndns(AirOsConverter): netjson_key = 'general' From cccea441d1f7d15eaebb9f3f2825823cb1652774 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 13:03:24 +0200 Subject: [PATCH 281/342] [airos] fixed config attribute for class airos converter --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 8ba41fa15..b7740a8fc 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -31,7 +31,7 @@ def should_run_forward(cls, config): @property def netmode(self): - return self.config.get('netmode', 'bridge') + return self.netjson.get('netmode', 'bridge') class Aaa(AirOsConverter): From f45da12fade0f95a951350babfa7a1bb1c6a7982 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 13:04:05 +0200 Subject: [PATCH 282/342] [airos] used property netmode insted of querying netjson --- netjsonconfig/backends/airos/converters.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index b7740a8fc..ac5f59484 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -174,8 +174,7 @@ def router_intermediate(self): return [{}] def to_intermediate(self): - netmode = get_copy(self.netjson, 'netmode', 'bridge') - result = getattr(self, '{netmode}_intermediate'.format(netmode=netmode))() + result = getattr(self, '{netmode}_intermediate'.format(netmode=self.netmode))() return (('ebtables', result),) @@ -263,8 +262,7 @@ def router_intermediate(self): return [base] def to_intermediate(self): - netmode = get_copy(self.netjson, 'netmode', 'bridge') - result = getattr(self, '{netmode}_intermediate'.format(netmode=netmode))() + result = getattr(self, '{netmode}_intermediate'.format(netmode=self.netmode))() return (('iptables', result),) @@ -329,7 +327,7 @@ class Netmode(AirOsConverter): def to_intermediate(self): result = [] result.append({ - 'status': self.netjson.get('netmode', 'bridge'), + 'status': self.netmode, }) return (('netmode', result), ) From a84e51af3603e46b7f38113816226f4d6af13bfa Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 17:12:40 +0200 Subject: [PATCH 283/342] [airos] used encryption in protocol function --- netjsonconfig/backends/airos/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index 232325573..0dc0a7473 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -71,7 +71,7 @@ def protocol(interface): """ Return wireless interface encryption """ - return interface['wireless']['encryption']['protocol'] + return encryption(interface)['protocol'] def psk(interface): From 78e136c1957d383fafee861db9b50478e2cbbd6a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 17:13:34 +0200 Subject: [PATCH 284/342] [airos] add test for interface functions with default values --- tests/airos/test_interface.py | 159 ++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 tests/airos/test_interface.py diff --git a/tests/airos/test_interface.py b/tests/airos/test_interface.py new file mode 100644 index 000000000..2337c07a7 --- /dev/null +++ b/tests/airos/test_interface.py @@ -0,0 +1,159 @@ +from unittest import TestCase +from netjsonconfig.backends.airos.interface import (autonegotiation, bridge, + bssid, encryption, + flowcontrol, hidden_ssid, + mode, protocol, psk, radio, + split_cidr, ssid, stp, vlan, + wireless) + +class InterfaceTest(TestCase): + def test_autonegotiation(self): + enabled = { + 'type': "ethernet", + 'name': "eth0", + 'autoneg': True, + } + disabled = { + 'type': "ethernet", + 'name': "eth0", + 'autoneg': False, + } + missing = { + 'type': "ethernet", + 'name': "eth0", + } + self.assertEqual(autonegotiation(enabled), 'enabled') + self.assertEqual(autonegotiation(disabled), 'disabled') + self.assertEqual(autonegotiation(missing), 'disabled') + + def test_bssid(self): + present = { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'ssid': 'ubnt', + 'bssid': '00:11:22:33:44:55', + } + } + missing = { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'ssid': 'ubnt', + } + } + + self.assertEqual(bssid(present), '00:11:22:33:44:55') + self.assertEqual(bssid(missing), '') + + def test_encryption(self): + present = { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'ssid': 'ubnt', + 'encryption': { + 'protocol': 'wpa2_personal', + 'password': 'changeme', + }, + } + } + missing = { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'ssid': 'ubnt', + } + } + self.assertEqual(encryption(present), {'protocol': 'wpa2_personal', 'password': 'changeme'}) + self.assertEqual(encryption(missing), {'protocol': 'none'}) + + def test_flowcontrol(self): + enabled = { + 'type': "ethernet", + 'name': "eth0", + 'flowcontrol': True, + } + disabled = { + 'type': "ethernet", + 'name': "eth0", + 'flowcontrol': False, + } + missing = { + 'type': "ethernet", + 'name': "eth0", + } + expected_enabled = { + 'rx': { + 'status': 'enabled', + }, + 'tx': { + 'status': 'enabled', + }, + } + expected_disabled = { + 'rx': { + 'status': 'disabled', + }, + 'tx': { + 'status': 'disabled', + }, + } + self.assertEqual(flowcontrol(enabled), expected_enabled) + self.assertEqual(flowcontrol(disabled), expected_disabled) + self.assertEqual(flowcontrol(missing), expected_disabled) + + def test_hidden_ssid(self): + enabled = { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'ssid': 'ubnt', + 'hidden': True, + } + } + disabled = { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'ssid': 'ubnt', + 'hidden': False, + } + } + missing = { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'ssid': 'ubnt', + } + } + self.assertEqual(hidden_ssid(enabled), 'enabled') + self.assertEqual(hidden_ssid(disabled), 'disabled') + self.assertEqual(hidden_ssid(missing), 'disabled') + + def test_stp(self): + enabled = { + 'type': 'bridge', + 'name': 'br0', + 'stp': True, + } + disabled = { + 'type': 'bridge', + 'name': 'br0', + 'stp': False, + } + missing = { + 'type': 'bridge', + 'name': 'br0', + } + self.assertEqual(stp(enabled), 'enabled') + self.assertEqual(stp(disabled), 'disabled') + self.assertEqual(stp(missing), 'disabled') + From 20d657ca52279737955ded46b470418fa7ca1f1c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 17:14:28 +0200 Subject: [PATCH 285/342] [airos] begun testing ebtables converter ebtables is configured with respect to mode and protocol of the wireless interface --- tests/airos/test_ebtables.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/airos/test_ebtables.py b/tests/airos/test_ebtables.py index f212b3b45..d665491cd 100644 --- a/tests/airos/test_ebtables.py +++ b/tests/airos/test_ebtables.py @@ -4,8 +4,21 @@ class EbtablesConverter(ConverterTest): backend = EbtablesAirOs - def test_ebtables(self): - o = self.backend({}) + def test_station_none(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'bssid': '00:11:22:33:44:55', + } + } + ] + }) o.to_intermediate() expected = [ { From 8f1a15cf7beff2239a5eb49bb789448d774ef9ab Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 18:02:44 +0200 Subject: [PATCH 286/342] [airos] moved ebtables to module, added tests for station mode --- netjsonconfig/backends/airos/converters.py | 19 ++- netjsonconfig/backends/airos/ebtables.py | 56 ++++++++ tests/airos/test_ebtables.py | 159 +++++++++++++++++++++ 3 files changed, 223 insertions(+), 11 deletions(-) create mode 100644 netjsonconfig/backends/airos/ebtables.py diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index ac5f59484..499291163 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,6 +4,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface +from .ebtables import ebtables_from_interface from .interface import (autonegotiation, bridge, flowcontrol, mode, protocol, radio, split_cidr, stp, vlan, wireless) from .radio import radio_available_mode, radio_configuration @@ -156,19 +157,15 @@ def to_intermediate(self): class Ebtables(AirOsConverter): netjson_key = 'general' - _base = { - 'sys': { - 'fw': { - 'status': 'disabled', - }, - 'status': 'enabled' - }, - 'status': 'enabled' - } + @property + def wireless(self): + """ + Return all the wireless interfaces + """ + return wireless(get_copy(self.netjson, 'interfaces', [])) def bridge_intermediate(self): - base = self._base.copy() - return [base] + return ebtables_from_interface(self.wireless[0]) def router_intermediate(self): return [{}] diff --git a/netjsonconfig/backends/airos/ebtables.py b/netjsonconfig/backends/airos/ebtables.py new file mode 100644 index 000000000..f22ea90bf --- /dev/null +++ b/netjsonconfig/backends/airos/ebtables.py @@ -0,0 +1,56 @@ +from .interface import mode, protocol, radio +import copy + + +def default(interace): + return {} + + +def station(interface): + """ + Returns the configuration for ``ebtables.sys`` + for an interface in ``station`` mode with ``wpa2_enterpise`` + or ``wpa2_personal`` authentication + """ + return { + 'eap': [ + { + 'devname': radio(interface), + 'status': 'enabled', + } + ], + 'eap.status': 'enabled', + } + + +_base = { + 'sys': { + 'fw': {'status': 'disabled'}, + 'status': 'enabled', + }, +} + +_status = { + 'status': 'enabled', +} + + +_mapping = { + 'access_point': { + 'none': default, + 'wpa2_personal': default, + 'wpa2_enterprise': default, + }, + 'station': { + 'none': default, + 'wpa2_personal': station, + 'wpa2_enterprise': station, + }, +} + + +def ebtables_from_interface(interface): + base = copy.deepcopy(_base) + status = _status.copy() + base['sys'].update(_mapping[mode(interface)][protocol(interface)](interface)) + return [status, base] diff --git a/tests/airos/test_ebtables.py b/tests/airos/test_ebtables.py index d665491cd..c84349e74 100644 --- a/tests/airos/test_ebtables.py +++ b/tests/airos/test_ebtables.py @@ -1,3 +1,4 @@ +from unittest import skip from .mock import ConverterTest, EbtablesAirOs @@ -21,10 +22,168 @@ def test_station_none(self): }) o.to_intermediate() expected = [ + { + 'status': 'enabled', + }, + { + 'sys.fw.status': 'disabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + def test_station_psk(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'bssid': '00:11:22:33:44:55', + 'encryption': { + 'protocol': 'wpa2_personal', + 'key': 'changeme', + } + } + } + ] + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + 'sys.eap.1.devname': 'ath0', + 'sys.eap.1.status': 'enabled', + 'sys.eap.status': 'enabled', + 'sys.fw.status': 'disabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + def test_station_eap(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'bssid': '00:11:22:33:44:55', + 'encryption': { + 'protocol': 'wpa2_enterprise', + 'identity': 'name@domain.com', + 'key': 'changeme', + } + } + } + ] + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + 'sys.eap.1.devname': 'ath0', + 'sys.eap.1.status': 'enabled', + 'sys.eap.status': 'enabled', + 'sys.fw.status': 'disabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + def test_access_none(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'access_point', + 'radio': 'ath0', + 'ssid': 'ubnt', + } + } + ] + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, { 'sys.fw.status': 'disabled', 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + def test_access_psk(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'access_point', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'encryption': { + 'protocol': 'wpa2_personal', + 'key': 'changeme', + } + } + } + ] + }) + o.to_intermediate() + expected = [ + { 'status': 'enabled', }, + { + 'sys.fw.status': 'disabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + @skip + def test_access_eap(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'access_point', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'encryption': { + 'protocol': 'wpa2_enterprise', + 'server': '192.168.1.1', + } + } + } + ] + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + 'sys.fw.status': 'disabled', + 'sys.status': 'enabled', + }, ] self.assertEqualConfig(o.intermediate_data['ebtables'], expected) From 6b394d8c916344ed7db36f6128df0325794c4eb2 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 10 Aug 2017 19:10:22 +0200 Subject: [PATCH 287/342] [airos] updated ebtable module for router and wpa2_personal --- netjsonconfig/backends/airos/converters.py | 4 ++- netjsonconfig/backends/airos/ebtables.py | 29 ++++++++++++---------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 499291163..ec13a2eb1 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -168,7 +168,9 @@ def bridge_intermediate(self): return ebtables_from_interface(self.wireless[0]) def router_intermediate(self): - return [{}] + result = ebtables_from_interface(self.wireless[0]) + del result['sys']['fw'] + return result def to_intermediate(self): result = getattr(self, '{netmode}_intermediate'.format(netmode=self.netmode))() diff --git a/netjsonconfig/backends/airos/ebtables.py b/netjsonconfig/backends/airos/ebtables.py index f22ea90bf..1631748db 100644 --- a/netjsonconfig/backends/airos/ebtables.py +++ b/netjsonconfig/backends/airos/ebtables.py @@ -1,6 +1,15 @@ -from .interface import mode, protocol, radio import copy +from .interface import mode, protocol, radio + + +_base = { + 'sys': { + 'fw': {'status': 'disabled'}, + 'status': 'enabled', + }, +} + def default(interace): return {} @@ -12,7 +21,8 @@ def station(interface): for an interface in ``station`` mode with ``wpa2_enterpise`` or ``wpa2_personal`` authentication """ - return { + base = copy.deepcopy(_base) + base['sys'].update({ 'eap': [ { 'devname': radio(interface), @@ -20,15 +30,9 @@ def station(interface): } ], 'eap.status': 'enabled', - } - + }) + return base -_base = { - 'sys': { - 'fw': {'status': 'disabled'}, - 'status': 'enabled', - }, -} _status = { 'status': 'enabled', @@ -38,7 +42,7 @@ def station(interface): _mapping = { 'access_point': { 'none': default, - 'wpa2_personal': default, + 'wpa2_personal': station, 'wpa2_enterprise': default, }, 'station': { @@ -50,7 +54,6 @@ def station(interface): def ebtables_from_interface(interface): - base = copy.deepcopy(_base) status = _status.copy() - base['sys'].update(_mapping[mode(interface)][protocol(interface)](interface)) + base = _mapping[mode(interface)][protocol(interface)](interface) return [status, base] From 53428f94060ca73c888676331bba7c7faf55db7c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 11 Aug 2017 15:03:58 +0200 Subject: [PATCH 288/342] [airos] updated ebtables converter and tests --- netjsonconfig/backends/airos/converters.py | 23 ++- netjsonconfig/backends/airos/ebtables.py | 43 +---- tests/airos/test_ebtables.py | 190 ++++++++++++++++++++- 3 files changed, 211 insertions(+), 45 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index ec13a2eb1..9868564bb 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -4,7 +4,7 @@ from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface -from .ebtables import ebtables_from_interface +from .ebtables import encrypted, unencrypted from .interface import (autonegotiation, bridge, flowcontrol, mode, protocol, radio, split_cidr, stp, vlan, wireless) from .radio import radio_available_mode, radio_configuration @@ -164,17 +164,22 @@ def wireless(self): """ return wireless(get_copy(self.netjson, 'interfaces', [])) - def bridge_intermediate(self): - return ebtables_from_interface(self.wireless[0]) + @property + def ebtables(self): + w = self.wireless[0] + status = {'status': 'enabled'} + base = {} + if protocol(w) == 'none': + base.update(unencrypted(w)) + else: + base.update(encrypted(w)) + if self.netmode == 'bridge': + base['sys'].update({'fw': {'status': 'disabled'}}) - def router_intermediate(self): - result = ebtables_from_interface(self.wireless[0]) - del result['sys']['fw'] - return result + return [status, base] def to_intermediate(self): - result = getattr(self, '{netmode}_intermediate'.format(netmode=self.netmode))() - return (('ebtables', result),) + return (('ebtables', self.ebtables),) class Gui(AirOsConverter): diff --git a/netjsonconfig/backends/airos/ebtables.py b/netjsonconfig/backends/airos/ebtables.py index 1631748db..b822434d5 100644 --- a/netjsonconfig/backends/airos/ebtables.py +++ b/netjsonconfig/backends/airos/ebtables.py @@ -1,25 +1,17 @@ import copy -from .interface import mode, protocol, radio +from .interface import protocol, radio _base = { 'sys': { - 'fw': {'status': 'disabled'}, 'status': 'enabled', }, } - -def default(interace): - return {} - - -def station(interface): +def encrypted(interface): """ - Returns the configuration for ``ebtables.sys`` - for an interface in ``station`` mode with ``wpa2_enterpise`` - or ``wpa2_personal`` authentication + Returns the configuration for ``ebtables.sys`` when encrypted """ base = copy.deepcopy(_base) base['sys'].update({ @@ -34,26 +26,9 @@ def station(interface): return base -_status = { - 'status': 'enabled', -} - - -_mapping = { - 'access_point': { - 'none': default, - 'wpa2_personal': station, - 'wpa2_enterprise': default, - }, - 'station': { - 'none': default, - 'wpa2_personal': station, - 'wpa2_enterprise': station, - }, -} - - -def ebtables_from_interface(interface): - status = _status.copy() - base = _mapping[mode(interface)][protocol(interface)](interface) - return [status, base] +def unencrypted(interface): + """ + Returns the configuration for ``ebtables.sys`` + for an interface withouth encryption + """ + return {} diff --git a/tests/airos/test_ebtables.py b/tests/airos/test_ebtables.py index c84349e74..9197aec61 100644 --- a/tests/airos/test_ebtables.py +++ b/tests/airos/test_ebtables.py @@ -1,8 +1,9 @@ from unittest import skip + from .mock import ConverterTest, EbtablesAirOs -class EbtablesConverter(ConverterTest): +class EbtablesConverterBridge(ConverterTest): backend = EbtablesAirOs def test_station_none(self): @@ -151,13 +152,15 @@ def test_access_psk(self): 'status': 'enabled', }, { + 'sys.eap.1.devname': 'ath0', + 'sys.eap.1.status': 'enabled', + 'sys.eap.status': 'enabled', 'sys.fw.status': 'disabled', 'sys.status': 'enabled', }, ] self.assertEqualConfig(o.intermediate_data['ebtables'], expected) - @skip def test_access_eap(self): o = self.backend({ 'interfaces': [ @@ -171,6 +174,7 @@ def test_access_eap(self): 'encryption': { 'protocol': 'wpa2_enterprise', 'server': '192.168.1.1', + 'key': 'changeme', } } } @@ -182,8 +186,190 @@ def test_access_eap(self): 'status': 'enabled', }, { + 'sys.eap.1.devname': 'ath0', + 'sys.eap.1.status': 'enabled', + 'sys.eap.status': 'enabled', 'sys.fw.status': 'disabled', 'sys.status': 'enabled', }, ] self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + +class EbtablesConverterRouter(ConverterTest): + backend = EbtablesAirOs + + def test_station_none(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'bssid': '00:11:22:33:44:55', + } + } + ], + "netmode": "router", + }) + o.to_intermediate() + expected = [] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + def test_station_psk(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'bssid': '00:11:22:33:44:55', + 'encryption': { + 'protocol': 'wpa2_personal', + 'key': 'changeme', + } + } + } + ], + "netmode": "router", + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + 'sys.eap.1.devname': 'ath0', + 'sys.eap.1.status': 'enabled', + 'sys.eap.status': 'enabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + def test_station_eap(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'station', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'bssid': '00:11:22:33:44:55', + 'encryption': { + 'protocol': 'wpa2_enterprise', + 'identity': 'name@domain.com', + 'key': 'changeme', + } + } + } + ], + "netmode": "router", + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + 'sys.eap.1.devname': 'ath0', + 'sys.eap.1.status': 'enabled', + 'sys.eap.status': 'enabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + def test_access_none(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'access_point', + 'radio': 'ath0', + 'ssid': 'ubnt', + } + } + ], + "netmode": "router", + }) + o.to_intermediate() + expected = [] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + def test_access_psk(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'access_point', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'encryption': { + 'protocol': 'wpa2_personal', + 'key': 'changeme', + } + } + } + ], + "netmode": "router", + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + 'sys.eap.1.devname': 'ath0', + 'sys.eap.1.status': 'enabled', + 'sys.eap.status': 'enabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) + + @skip + def test_access_eap(self): + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'wireless': { + 'mode': 'access_point', + 'radio': 'ath0', + 'ssid': 'ubnt', + 'encryption': { + 'protocol': 'wpa2_enterprise', + 'server': '192.168.1.1', + } + } + } + ], + "netmode": "router", + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + 'sys.eap.1.devname': 'ath0', + 'sys.eap.1.status': 'enabled', + 'sys.eap.status': 'enabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['ebtables'], expected) From 56e4b8aca8060358afee8b7e87f150ab340c6e0c Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 11 Aug 2017 16:42:52 +0200 Subject: [PATCH 289/342] [airos] drafted support for vlan configuration in ebtables --- netjsonconfig/backends/airos/converters.py | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 9868564bb..41116ae41 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -157,6 +157,13 @@ def to_intermediate(self): class Ebtables(AirOsConverter): netjson_key = 'general' + @property + def vlan(self): + """ + Return all the vlan interfaces + """ + return vlan(get_copy(self.netjson, self.netjson_key, [])) + @property def wireless(self): """ @@ -175,7 +182,24 @@ def ebtables(self): base.update(encrypted(w)) if self.netmode == 'bridge': base['sys'].update({'fw': {'status': 'disabled'}}) - + vlans = [] + _t = { + 'devname': '', + 'id': '', + 'status': '', + } + for v in self.vlan: + t = _t.copy() + name_and_id = v['name'].split('.') + t.update({ + 'devname': name_and_id[0], + 'id': name_and_id[1], + 'status': status(v), + }) + vlans.append(t) + if vlans: + base['sys']['vlan.status'] = 'enabled' + base['sys']['vlan'] = vlans return [status, base] def to_intermediate(self): From 6c449f18b7a0d4366358c98f2b796a2919e07b9f Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 11:30:15 +0200 Subject: [PATCH 290/342] [airos][doc] added doc for ssh key --- docs/source/backends/airos.rst | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 3d44dd3dc..d5501e58c 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -194,6 +194,41 @@ For the lazy one we provide these defaults } } +Ssh +--- + +We can specify the configuration for the ssh server on the antenna using the ``sshd`` property. + +This snippet shows how to configure the ssh server with the default values. + +.. code-block:: json + + { + "type": "DeviceConfiguration", + "sshd": { + "port": 22, + "enabled": true, + "password_auth": true + } + } + +And this shows how to set the authorized ssh public keys + +.. code-block:: json + + { + "type": "DeviceConfiguration", + "sshd": { + "keys": [ + { + "type": "ssh-rsa", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBEEhdDJIbHVHIXQQ8dzH3pfmIbZjlrcIV+YkZM//ezQtINTUbqolCXFsETVVwbCH6d8Pi1v1lCDgILbkOOivTIKUgG8/84yI4VLCH03CAd55IG7IFZe9e6ThT4/MryH8zXKGAq5rnQSW90ashZaOEH0wNTOhkZmQ/QhduJcarevH4iZPrq5eM/ClCXzkF0I/EWN89xKRrjMB09WmuYOT48n5Es08iJxwQ1gKfjk84Fy+hwMKVtOssfBGuYMBWByJwuvW5xCH3H6eVr1GhkBRrlTy6KAkc9kfAsSpkHIyeb/jAS2hr6kAh6cxapKENHxoAdJNvMEpdU11v6PMoOtIb edoput@hypnotoad", + "comment": "my shh key", + "enabled": true + ] + } + } + Users ----- From 4f65561a3e5e63db11ff54fcada6eeaf75b05cd3 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 11:30:51 +0200 Subject: [PATCH 291/342] [airos] renamed dhcp converter to match section name --- netjsonconfig/backends/airos/airos.py | 10 +++--- netjsonconfig/backends/airos/converters.py | 2 +- tests/airos/mock.py | 10 ++++++ tests/airos/test_dhcpc.py | 36 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tests/airos/test_dhcpc.py diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index caa1a5cef..60fcf2e5f 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -1,11 +1,11 @@ from collections import OrderedDict from ..base.backend import BaseBackend -from .converters import (Aaa, Bridge, Discovery, Dyndns, Ebtables, Gui, Httpd, - Igmpproxy, Iptables, Netconf, Netmode, Ntpclient, - Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, - System, Telnetd, Tshaper, Unms, Update, Users, Vlan, - Wireless, Wpasupplicant) +from .converters import (Aaa, Bridge, Dhcpc, Discovery, Dyndns, Ebtables, Gui, + Httpd, Igmpproxy, Iptables, Netconf, Netmode, + Ntpclient, Pwdog, Radio, Resolv, Route, Snmp, Sshd, + Syslog, System, Telnetd, Tshaper, Unms, Update, Users, + Vlan, Wireless, Wpasupplicant) from .intermediate import flatten, intermediate_to_list from .renderers import AirOsRenderer from .schema import schema diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 41116ae41..a2eeb1470 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -122,7 +122,7 @@ def to_intermediate(self): return (('discovery', result),) -class Dhcp(AirOsConverter): +class Dhcpc(AirOsConverter): @classmethod def should_run_forward(cls, config): diff --git a/tests/airos/mock.py b/tests/airos/mock.py index 59459db19..c9f1a6f0d 100644 --- a/tests/airos/mock.py +++ b/tests/airos/mock.py @@ -3,6 +3,7 @@ from netjsonconfig import AirOs from netjsonconfig.backends.airos.airos import to_ordered_list from netjsonconfig.backends.airos.converters import (Aaa, Bridge, Discovery, + Dhcpc, Dyndns, Ebtables, Gui, Httpd, Igmpproxy, Iptables, Netconf, @@ -57,6 +58,15 @@ class BridgeAirOs(AirOs): ] +class DhcpcAirOs(AirOs): + """ + Mock backend with converter for network hardware discovery + """ + converters = [ + Dhcpc, + ] + + class DiscoveryAirOs(AirOs): """ Mock backend with converter for network hardware discovery diff --git a/tests/airos/test_dhcpc.py b/tests/airos/test_dhcpc.py new file mode 100644 index 000000000..6253d8e9a --- /dev/null +++ b/tests/airos/test_dhcpc.py @@ -0,0 +1,36 @@ +from .mock import ConverterTest, DhcpcAirOs + + +class TestNetmodeConverter(ConverterTest): + + backend = DhcpcAirOs + + def test_bridge(self): + o = self.backend({ + 'netmode': 'bridge', + }) + o.to_intermediate() + + with self.assertRaises(KeyError): + o.intermediate_data['dhcpc'] + + def test_router(self): + o = self.backend({ + 'netmode': 'router', + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + [ + { + 'devname': 'br0', + 'fallback': '192.168.10.1', + 'fallback_netmask': '255.255.255.0', + 'status': 'enabled', + }, + ] + ] + + self.assertEqualConfig(o.intermediate_data['dhcpc'], expected) From daa00e34bfd9a66945f899906a8d63a34e95746d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 11:42:25 +0200 Subject: [PATCH 292/342] [airos] added test for sshd converter --- tests/airos/test_sshd.py | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/airos/test_sshd.py diff --git a/tests/airos/test_sshd.py b/tests/airos/test_sshd.py new file mode 100644 index 000000000..fc6fa6afd --- /dev/null +++ b/tests/airos/test_sshd.py @@ -0,0 +1,55 @@ +from .mock import ConverterTest, SshdAirOs + + +class TestSshdConverter(ConverterTest): + + backend = SshdAirOs + + def test_with_password(self): + o = self.backend({ + 'sshd': { + 'port': 22, + 'enabled': True, + 'password_auth': True, + }, + }) + o.to_intermediate() + expected = [ + { + 'auth.passwd': 'enabled', + 'port': 22, + 'status': 'enabled', + } + ] + self.assertEqualConfig(o.intermediate_data['sshd'], expected) + + def test_with_key(self): + o = self.backend({ + 'sshd': { + 'port': 22, + 'enabled': True, + 'password_auth': True, + 'keys': [ + { + 'type': 'ssh-rsa', + 'key': 'my-public-key-here', + 'comment': 'this is netjsonconfig pubkey', + 'enabled': True, + } + ] + }, + }) + o.to_intermediate() + expected = [ + { + 'auth.passwd': 'enabled', + 'auth.key.1.status': 'enabled', + 'auth.key.1.type': 'ssh-rsa', + 'auth.key.1.value': 'my-public-key-here', + 'auth.key.1.comment': 'this is netjsonconfig pubkey', + 'port': 22, + 'status': 'enabled', + } + ] + + self.assertEqualConfig(o.intermediate_data['sshd'], expected) From 96db8a7511e078a412dd46275d0d1634f5f9f68a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 12:16:51 +0200 Subject: [PATCH 293/342] [airos][test] added test for route converter --- tests/airos/test_route.py | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/airos/test_route.py diff --git a/tests/airos/test_route.py b/tests/airos/test_route.py new file mode 100644 index 000000000..9118df552 --- /dev/null +++ b/tests/airos/test_route.py @@ -0,0 +1,64 @@ +from unittest import skip + +from .mock import ConverterTest, RouteAirOs + + +class TestRouteConverter(ConverterTest): + + backend = RouteAirOs + + def test_gateway_interface(self): + o = self.backend({ + 'interfaces': [{ + 'name': 'eth0', + 'type': 'ethernet', + 'addresses': [ + { + 'family': 'ipv4', + 'proto': 'dhcp', + 'gateway': '192.168.0.1' + } + ] + }] + }) + + o.to_intermediate() + + expected = [ + { + '1.devname': 'eth0', + '1.gateway': '192.168.0.1', + '1.ip': '0.0.0.0', + '1.netmask': 0, + '1.status': 'enabled', + }, + ] + + self.assertEqualConfig(o.intermediate_data['route'], expected) + + def test_user_route(self): + o = self.backend({ + 'routes': [ + { + 'cost': 0, + 'destination': '192.178.1.0/24', + 'device': 'br0', + 'next': '192.178.1.1', + 'source': '192.168.1.20', + } + ] + }) + o.to_intermediate() + expected = [ + { + '1.ip': '192.178.1.0', + '1.netmask': '255.255.255.0', + '1.gateway': '192.178.1.1', + '1.status': 'enabled', + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqualConfig(o.intermediate_data['route'], expected) From 35a532df8c8ce7ef1f793e9a35ca6e7f49a8c154 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 12:34:27 +0200 Subject: [PATCH 294/342] [airos] added test for wireless converter in station mode --- tests/airos/test_wireless.py | 189 ++++++++++++++++++++++++++++------- 1 file changed, 155 insertions(+), 34 deletions(-) diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index c62f96643..bcb74303f 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -1,32 +1,32 @@ from .mock import ConverterTest, WirelessAirOs -class TestWirelessConverter(ConverterTest): +class TestWirelessAccessConverter(ConverterTest): backend = WirelessAirOs def test_active_wireless(self): o = self.backend({ - "interfaces": [ + 'interfaces': [ { - "type": "wireless", - "name": "wlan0", - "mac": "de:9f:db:30:c9:c5", - "mtu": 1500, - "txqueuelen": 1000, - "autostart": True, - "wireless": { - "radio": "radio0", - "mode": "access_point", - "ssid": "ap-ssid-example", + 'type': 'wireless', + 'name': 'wlan0', + 'mac': 'de:9f:db:30:c9:c5', + 'mtu': 1500, + 'txqueuelen': 1000, + 'autostart': True, + 'wireless': { + 'radio': 'radio0', + 'mode': 'access_point', + 'ssid': 'ap-ssid-example', }, - "addresses": [ + 'addresses': [ { - "address": "192.168.1.1", - "mask": 24, - "family": "ipv4", - "proto": "static", + 'address': '192.168.1.1', + 'mask': 24, + 'family': 'ipv4', + 'proto': 'static', } ], } @@ -65,26 +65,147 @@ def test_active_wireless(self): def test_inactive_wireless(self): o = self.backend({ - "interfaces": [ + 'interfaces': [ { - "type": "wireless", - "name": "wlan0", - "mac": "de:9f:db:30:c9:c5", - "mtu": 1500, - "txqueuelen": 1000, - "autostart": True, - "disabled": True, - "wireless": { - "radio": "radio0", - "mode": "access_point", - "ssid": "ap-ssid-example", + 'type': 'wireless', + 'name': 'wlan0', + 'mac': 'de:9f:db:30:c9:c5', + 'mtu': 1500, + 'txqueuelen': 1000, + 'autostart': True, + 'disabled': True, + 'wireless': { + 'radio': 'radio0', + 'mode': 'access_point', + 'ssid': 'ap-ssid-example', }, - "addresses": [ + 'addresses': [ { - "address": "192.168.1.1", - "mask": 24, - "family": "ipv4", - "proto": "static", + 'address': '192.168.1.1', + 'mask': 24, + 'family': 'ipv4', + 'proto': 'static', + } + ], + } + ] + }) + o.to_intermediate() + expected = [ + { + '1.addmtikie': 'enabled', + '1.devname': 'radio0', + '1.hide_ssid': 'disabled', + '1.l2_isolation': 'disabled', + '1.mac_acl.policy': 'allow', + '1.mac_acl.status': 'disabled', + '1.mcast.enhance': 0, + '1.rate.auto': 'enabled', + '1.rate.mcs': -1, + '1.security.type': 'none', + '1.signal_led1': 75, + '1.signal_led2': 50, + '1.signal_led3': 25, + '1.signal_led4': 15, + '1.signal_led_status': 'enabled', + '1.ssid': 'ap-ssid-example', + '1.status': 'disabled', + '1.wds.status': 'enabled', + }, + { + 'status': 'enabled', + } + ] + + self.assertEqualConfig(o.intermediate_data['wireless'], expected) + + +class TestWirelessStationConverter(ConverterTest): + + backend = WirelessAirOs + + def test_active_wireless(self): + + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'mac': 'de:9f:db:30:c9:c5', + 'mtu': 1500, + 'txqueuelen': 1000, + 'autostart': True, + 'wireless': { + 'radio': 'radio0', + 'mode': 'station', + 'ssid': 'ap-ssid-example', + 'bssid': '00:11:22:33:44:55' + }, + 'addresses': [ + { + 'address': '192.168.1.1', + 'mask': 24, + 'family': 'ipv4', + 'proto': 'static', + } + ], + } + ] + }) + o.to_intermediate() + expected = [ + { + '1.addmtikie': 'enabled', + '1.devname': 'radio0', + '1.hide_ssid': 'disabled', + '1.l2_isolation': 'disabled', + '1.mac_acl.policy': 'allow', + '1.mac_acl.status': 'disabled', + '1.mcast.enhance': 0, + '1.rate.auto': 'enabled', + '1.rate.mcs': -1, + '1.security.type': 'none', + '1.signal_led1': 75, + '1.signal_led2': 50, + '1.signal_led3': 25, + '1.signal_led4': 15, + '1.signal_led_status': 'enabled', + '1.ssid': 'ap-ssid-example', + '1.status': 'enabled', + '1.wds.status': 'enabled', + + }, + { + 'status': 'enabled', + } + ] + + self.assertEqualConfig(o.intermediate_data['wireless'], expected) + + def test_inactive_wireless(self): + + o = self.backend({ + 'interfaces': [ + { + 'type': 'wireless', + 'name': 'wlan0', + 'mac': 'de:9f:db:30:c9:c5', + 'mtu': 1500, + 'txqueuelen': 1000, + 'autostart': True, + 'disabled': True, + 'wireless': { + 'radio': 'radio0', + 'mode': 'station', + 'ssid': 'ap-ssid-example', + 'bssid': '00:11:22:33:44:55', + }, + 'addresses': [ + { + 'address': '192.168.1.1', + 'mask': 24, + 'family': 'ipv4', + 'proto': 'static', } ], } From b213d990edf23e862f4ca55a02ae59439cac9327 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 12:47:09 +0200 Subject: [PATCH 295/342] [airos] added dhcpc converter to backend converters --- netjsonconfig/backends/airos/airos.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 60fcf2e5f..884d58ff1 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -27,6 +27,7 @@ class AirOs(BaseBackend): converters = [ Aaa, Bridge, + Dhcpc, Discovery, Dyndns, Ebtables, From 480b56071d6509fbb00a60a09f2a1bf31f50281e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 12:51:11 +0200 Subject: [PATCH 296/342] [airos] make flake happier --- netjsonconfig/backends/airos/ebtables.py | 3 ++- tests/airos/test_interface.py | 11 ++++------- tests/airos/test_route.py | 2 -- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/netjsonconfig/backends/airos/ebtables.py b/netjsonconfig/backends/airos/ebtables.py index b822434d5..4071d15e2 100644 --- a/netjsonconfig/backends/airos/ebtables.py +++ b/netjsonconfig/backends/airos/ebtables.py @@ -1,6 +1,6 @@ import copy -from .interface import protocol, radio +from .interface import radio _base = { @@ -9,6 +9,7 @@ }, } + def encrypted(interface): """ Returns the configuration for ``ebtables.sys`` when encrypted diff --git a/tests/airos/test_interface.py b/tests/airos/test_interface.py index 2337c07a7..2ae72fa28 100644 --- a/tests/airos/test_interface.py +++ b/tests/airos/test_interface.py @@ -1,10 +1,8 @@ from unittest import TestCase -from netjsonconfig.backends.airos.interface import (autonegotiation, bridge, - bssid, encryption, - flowcontrol, hidden_ssid, - mode, protocol, psk, radio, - split_cidr, ssid, stp, vlan, - wireless) + +from netjsonconfig.backends.airos.interface import (autonegotiation, bssid, encryption, + flowcontrol, hidden_ssid, stp) + class InterfaceTest(TestCase): def test_autonegotiation(self): @@ -156,4 +154,3 @@ def test_stp(self): self.assertEqual(stp(enabled), 'enabled') self.assertEqual(stp(disabled), 'disabled') self.assertEqual(stp(missing), 'disabled') - diff --git a/tests/airos/test_route.py b/tests/airos/test_route.py index 9118df552..eab0ede3a 100644 --- a/tests/airos/test_route.py +++ b/tests/airos/test_route.py @@ -1,5 +1,3 @@ -from unittest import skip - from .mock import ConverterTest, RouteAirOs From fc5d7331a92c3456ec6c9ecdbd92c1c08a4bea36 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 13:15:48 +0200 Subject: [PATCH 297/342] [airos] split status and base in iptables converter --- netjsonconfig/backends/airos/converters.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index a2eeb1470..b0f92ca48 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -264,18 +264,20 @@ class Iptables(AirOsConverter): 'portfw': {'status': 'disabled'}, 'status': 'enabled', }, - 'status': 'disabled' + } + + _status = { + 'status': 'enabled', } def bridge_intermediate(self): base = self._base.copy() - return [base] + iptables_status = self._status.copy() + return [iptables_status, base] def router_intermediate(self): base = self._base.copy() - base.update({ - 'status': 'enabled', - }) + iptables_status = self._status.copy() base['sys'].update({ 'fw': {'status': 'disabled'}, 'mgmt': [ @@ -287,7 +289,7 @@ def router_intermediate(self): 'mgmt.status': 'enabled', }) - return [base] + return [iptables_status, base] def to_intermediate(self): result = getattr(self, '{netmode}_intermediate'.format(netmode=self.netmode))() From 73fa28befb98c230eb9d98d47c5e0d7e4b2a5439 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 15:01:51 +0200 Subject: [PATCH 298/342] [airos] added warning to documentation --- docs/source/backends/airos.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index d5501e58c..e36b3002c 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -6,6 +6,9 @@ AirOS Backend The ``AirOs`` backend allows to generate AirOS v8.3 compatible configurations. +.. warning:: + This backend is experimental, please read this document carefully to prevent misconfiguration problems + Initialization -------------- From 6c116940c0cc379c49787713d2ac363b12db2b06 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 15:07:12 +0200 Subject: [PATCH 299/342] [airos] make isort happier --- netjsonconfig/backends/airos/ebtables.py | 1 - tests/airos/mock.py | 21 ++++++++++----------- tests/airos/test_interface.py | 5 +++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/netjsonconfig/backends/airos/ebtables.py b/netjsonconfig/backends/airos/ebtables.py index 4071d15e2..7d0d5ea03 100644 --- a/netjsonconfig/backends/airos/ebtables.py +++ b/netjsonconfig/backends/airos/ebtables.py @@ -2,7 +2,6 @@ from .interface import radio - _base = { 'sys': { 'status': 'enabled', diff --git a/tests/airos/mock.py b/tests/airos/mock.py index c9f1a6f0d..401e9fba5 100644 --- a/tests/airos/mock.py +++ b/tests/airos/mock.py @@ -2,17 +2,16 @@ from netjsonconfig import AirOs from netjsonconfig.backends.airos.airos import to_ordered_list -from netjsonconfig.backends.airos.converters import (Aaa, Bridge, Discovery, - Dhcpc, - Dyndns, Ebtables, Gui, - Httpd, Igmpproxy, - Iptables, Netconf, - Netmode, Ntpclient, Pwdog, - Radio, Resolv, Route, - Snmp, Sshd, Syslog, - System, Telnetd, Update, - Users, Vlan, Wireless, - Wpasupplicant) +from netjsonconfig.backends.airos.converters import (Aaa, Bridge, Dhcpc, + Discovery, Dyndns, + Ebtables, Gui, Httpd, + Igmpproxy, Iptables, + Netconf, Netmode, + Ntpclient, Pwdog, Radio, + Resolv, Route, Snmp, Sshd, + Syslog, System, Telnetd, + Update, Users, Vlan, + Wireless, Wpasupplicant) class ConverterTest(TestCase): diff --git a/tests/airos/test_interface.py b/tests/airos/test_interface.py index 2ae72fa28..0776ad3ca 100644 --- a/tests/airos/test_interface.py +++ b/tests/airos/test_interface.py @@ -1,7 +1,8 @@ from unittest import TestCase -from netjsonconfig.backends.airos.interface import (autonegotiation, bssid, encryption, - flowcontrol, hidden_ssid, stp) +from netjsonconfig.backends.airos.interface import (autonegotiation, bssid, + encryption, flowcontrol, + hidden_ssid, stp) class InterfaceTest(TestCase): From 2f107655a0673646ba2225206c410a8f4262a808 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 15:32:46 +0200 Subject: [PATCH 300/342] [airos] fixed docs build issue --- docs/source/backends/airos.rst | 7 +++++++ docs/source/backends/intermediate.rst | 9 ++------- docs/source/index.rst | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index e36b3002c..a38f3e32c 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -9,6 +9,7 @@ The ``AirOs`` backend allows to generate AirOS v8.3 compatible configurations. .. warning:: This backend is experimental, please read this document carefully to prevent misconfiguration problems + Initialization -------------- @@ -56,6 +57,11 @@ JSON method .. automethod:: netjsonconfig.AirOs.json +Extending the backend +--------------------- + +Please see the :ref:`airos-intermediate-representation` page for extending converters and adding functionalities to this backend + General settings ---------------- @@ -228,6 +234,7 @@ And this shows how to set the authorized ssh public keys "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBEEhdDJIbHVHIXQQ8dzH3pfmIbZjlrcIV+YkZM//ezQtINTUbqolCXFsETVVwbCH6d8Pi1v1lCDgILbkOOivTIKUgG8/84yI4VLCH03CAd55IG7IFZe9e6ThT4/MryH8zXKGAq5rnQSW90ashZaOEH0wNTOhkZmQ/QhduJcarevH4iZPrq5eM/ClCXzkF0I/EWN89xKRrjMB09WmuYOT48n5Es08iJxwQ1gKfjk84Fy+hwMKVtOssfBGuYMBWByJwuvW5xCH3H6eVr1GhkBRrlTy6KAkc9kfAsSpkHIyeb/jAS2hr6kAh6cxapKENHxoAdJNvMEpdU11v6PMoOtIb edoput@hypnotoad", "comment": "my shh key", "enabled": true + } ] } } diff --git a/docs/source/backends/intermediate.rst b/docs/source/backends/intermediate.rst index 35d262120..d0b997590 100644 --- a/docs/source/backends/intermediate.rst +++ b/docs/source/backends/intermediate.rst @@ -1,14 +1,9 @@ -============= -AirOS Backend -============= - -.. include:: ../_github.rst - +.. _airos-intermediate-representation: Intermediate representation --------------------------- -The intermediate representation is the output of the a :ref:`converter`, +The intermediate representation is the output of the a **converter**, it is backend specific and is built as a tree structure made from python builtins values. diff --git a/docs/source/index.rst b/docs/source/index.rst index d8637a62d..304dff4ba 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -49,6 +49,7 @@ Contents: /general/setup /general/basics /backends/airos + /backends/intermediate /backends/openwrt /backends/openwisp /backends/openvpn From 8b08e44e052508b3611e341c331dd34532fb56b4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 15:49:28 +0200 Subject: [PATCH 301/342] [airos] install graphviz package on travis build --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index ca0673e15..c732876ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,11 @@ language: python sudo: false cache: pip +addons: + apt: + packages: + - graphviz + python: - "3.5" - "3.4" From d73a8870903adcfc63693700202a1999cea24803 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 14 Aug 2017 15:57:37 +0200 Subject: [PATCH 302/342] [airos] fixed graphviz syntax error --- docs/source/backends/intermediate.rst | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/source/backends/intermediate.rst b/docs/source/backends/intermediate.rst index d0b997590..86c681c6c 100644 --- a/docs/source/backends/intermediate.rst +++ b/docs/source/backends/intermediate.rst @@ -21,7 +21,8 @@ As an example here we present the tree `('spam', ['eggs', 'snakes'])` .. graphviz:: digraph tree { - spam -> {eggs, snakes}; + spam -> eggs; + spam -> snakes; } As a son may be a carrier of a value so we store it in a dictionary instead of adding a *leaf* @@ -97,7 +98,9 @@ And the resulting tree is: spam -> eggs; spam -> snakes -> loved; - loved -> {1,2,3}; + loved -> {1}; + loved -> {2}; + loved -> {3}; 1 -> python2; 2 -> python3; @@ -195,7 +198,8 @@ The tree associated with the previous NetJSON example is this: .. graphviz:: digraph tree { - vlan -> {1,2}; + vlan -> 1; + vlan -> 2; devname1 [label="devname=eth0"]; devname2 [label="devname=eth0"]; @@ -208,8 +212,14 @@ The tree associated with the previous NetJSON example is this: comment1 [label="comment=management"]; comment2 [label="comment=traffic"]; - 1 -> {devname1, id1, status1, comment1}; - 2 -> {devname2, id2, status2, comment2}; + 1 -> devname1; + 1 -> id1; + 1 -> status1; + 1 -> comment1; + 2 -> devname2; + 2 -> id2; + 2 -> status2; + 2 -> comment2; } And by exploring depth first we get to read a line of the configuration at a time. @@ -230,5 +240,7 @@ configuration `vlan.1.devname=eth0` comment1 [label="comment=management"]; 1 -> devname1 [color="blue"]; - 1 -> {id1, status1, comment1}; + 1 -> id1; + 1 -> status1; + 1 -> comment1; } From 1fecda20f1621095dfa03ab199c59f9861c92369 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 16 Aug 2017 10:43:17 +0200 Subject: [PATCH 303/342] [airos] fixed input for ebtables test --- tests/airos/test_ebtables.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/airos/test_ebtables.py b/tests/airos/test_ebtables.py index 9197aec61..de92700fd 100644 --- a/tests/airos/test_ebtables.py +++ b/tests/airos/test_ebtables.py @@ -1,5 +1,3 @@ -from unittest import skip - from .mock import ConverterTest, EbtablesAirOs @@ -340,7 +338,6 @@ def test_access_psk(self): ] self.assertEqualConfig(o.intermediate_data['ebtables'], expected) - @skip def test_access_eap(self): o = self.backend({ 'interfaces': [ @@ -354,6 +351,7 @@ def test_access_eap(self): 'encryption': { 'protocol': 'wpa2_enterprise', 'server': '192.168.1.1', + 'key': 'radius-change-me', } } } From 48aca5703f5af310d073004c0ecc45d82feea83e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 16 Aug 2017 10:54:56 +0200 Subject: [PATCH 304/342] [airos] removed comment from test wpa2_personal with eap authentication requires the key aaa.1.devname both in bridge and router mode --- tests/airos/test_aaa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/airos/test_aaa.py b/tests/airos/test_aaa.py index 9576563b6..cb9e87e52 100644 --- a/tests/airos/test_aaa.py +++ b/tests/airos/test_aaa.py @@ -125,7 +125,7 @@ def test_ap_eap_authentication(self): 'status': 'enabled', }, { - '1.br.devname': 'br0', # only in bridge mode? + '1.br.devname': 'br0', '1.devname': 'ath0', '1.driver': 'madwifi', '1.radius.acct.1.port': 1813, From 91d56aab28291412e20a5512d18d8582008744aa Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 16 Aug 2017 12:13:34 +0200 Subject: [PATCH 305/342] [airos] update expected configuration for wpasupplicant acess_point --- tests/airos/test_wpasupplicant.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index ff86f16b5..095b2527c 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -344,7 +344,11 @@ def test_eap_wpa2_enterprise(self): 'status': 'disabled', }, { + 'device.1.profile': 'AUTO', 'device.1.status': 'disabled', + 'profile.1.name': 'AUTO', + 'profile.1.network.1.key_mgmt.1.name': 'NONE', + 'profile.1.network.1.priority': 100, 'profile.1.network.1.ssid': 'ap-ssid-example', 'profile.1.network.2.key_mgmt.1.name': 'NONE', 'profile.1.network.2.priority': 2, From 68c47151ae6620d6812db0a5b8244e96b192a8bf Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 16 Aug 2017 12:54:30 +0200 Subject: [PATCH 306/342] [airos] rebased and folded wpasupplicant module --- netjsonconfig/backends/airos/wpasupplicant.py | 96 +++++-------------- 1 file changed, 23 insertions(+), 73 deletions(-) diff --git a/netjsonconfig/backends/airos/wpasupplicant.py b/netjsonconfig/backends/airos/wpasupplicant.py index 0f6f37edc..25c1374a8 100644 --- a/netjsonconfig/backends/airos/wpasupplicant.py +++ b/netjsonconfig/backends/airos/wpasupplicant.py @@ -1,4 +1,4 @@ -from .interface import encryption, ssid +from .interface import bssid, encryption, ssid def ap_no_encryption(interface): @@ -9,11 +9,7 @@ def ap_no_encryption(interface): return { 'ssid': ssid(interface), 'priority': 100, - 'key_mgmt': [ - { - 'name': 'NONE', - }, - ], + 'key_mgmt': [{'name': 'NONE'}], } @@ -23,16 +19,9 @@ def ap_wpa2_personal(interface): for wpa2_personal as the indernediate dict in ``access_point`` mode """ - return { - 'psk': encryption(interface)['key'], - 'ssid': ssid(interface), - 'priority': 100, - 'key_mgmt': [ - { - 'name': 'NONE', - }, - ], - } + base = ap_no_encryption(interface) + base.update({'psk': encryption(interface)['key']}) + return base def ap_wpa2_enterprise(interface): @@ -41,12 +30,9 @@ def ap_wpa2_enterprise(interface): for wpa2_personal as the indernediate dict in ``access_point`` mode """ - return { - 'ssid': ssid(interface), - } + return ap_no_encryption(interface) -# STATION def sta_no_encryption(interface): """ Returns the wpasupplicant.profile.1.network @@ -56,11 +42,7 @@ def sta_no_encryption(interface): return { 'ssid': ssid(interface), 'priority': 100, - 'key_mgmt': [ - { - 'name': 'NONE', - }, - ], + 'key_mgmt': [{'name': 'NONE'}], } @@ -70,37 +52,16 @@ def sta_wpa2_personal(interface): for wpa2_personal as the indernediate dict in ``station`` mode """ - return { - 'ssid': ssid(interface), + base = sta_no_encryption(interface) + base.update({ 'psk': encryption(interface)['key'], - # no advanced authentication methods - # with psk - 'eap': [ - { - 'status': 'disabled', - }, - ], - 'key_mgmt': [ - { - 'name': 'WPA-PSK', - }, - ], - 'pairwise': [ - { - 'name': 'CCMP', - }, - ], - # this may be not necessary - # as further authentication is not - # supported + 'eap': [{'status': 'disabled'}], + 'key_mgmt': [{'name': 'WPA-PSK'}], + 'pairwise': [{'name': 'CCMP'}], 'phase2=auth': 'MSCHAPV2', - 'priority': 100, - 'proto': [ - { - 'name': 'RSN', - }, - ], - } + 'proto': [{'name': 'RSN'}], + }) + return base def sta_wpa2_enterprise(interface): @@ -108,8 +69,9 @@ def sta_wpa2_enterprise(interface): Returns the wpasupplicant.profile.1.network for wpa2_enterprise as the intermediate dict """ - return { - 'ssid': ssid(interface), + base = ap_no_encryption(interface) + base.update({ + 'bssid': bssid(interface), 'phase2=auth': 'MSCHAPV2', 'eap': [ { @@ -119,23 +81,11 @@ def sta_wpa2_enterprise(interface): ], 'password': encryption(interface)['password'], 'identity': encryption(interface)['identity'], - 'pairwise': [ - { - 'name': 'CCMP', - }, - ], - 'proto': [ - { - 'name': 'RSN', - }, - ], - 'priority': 100, - 'key_mgmt': [ - { - 'name': 'WPA-EAP', - }, - ], - } + 'pairwise': [{'name': 'CCMP'}], + 'proto': [{'name': 'RSN'}], + 'key_mgmt': [{'name': 'WPA-EAP'}], + }) + return base available_mode_authentication = { From bc426cfece391c7c8c582789beea5807410757bf Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 16 Aug 2017 12:55:10 +0200 Subject: [PATCH 307/342] [airos] sorted and updated test for wpasupplicant section --- tests/airos/test_wpasupplicant.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/airos/test_wpasupplicant.py b/tests/airos/test_wpasupplicant.py index 095b2527c..1adcc703f 100644 --- a/tests/airos/test_wpasupplicant.py +++ b/tests/airos/test_wpasupplicant.py @@ -99,19 +99,19 @@ def test_wpa2_personal(self): o.to_intermediate() expected = [ { + 'device.1.devname': 'radio0', + 'device.1.driver': 'madwifi', 'device.1.profile': 'AUTO', 'device.1.status': 'enabled', - 'device.1.driver': 'madwifi', - 'device.1.devname': 'radio0', 'profile.1.name': 'AUTO', - 'profile.1.network.1.phase2=auth': 'MSCHAPV2', 'profile.1.network.1.eap.1.status': 'disabled', - 'profile.1.network.1.psk': 'cucumber', + 'profile.1.network.1.key_mgmt.1.name': 'WPA-PSK', 'profile.1.network.1.pairwise.1.name': 'CCMP', + 'profile.1.network.1.phase2=auth': 'MSCHAPV2', + 'profile.1.network.1.priority': 100, 'profile.1.network.1.proto.1.name': 'RSN', + 'profile.1.network.1.psk': 'cucumber', 'profile.1.network.1.ssid': 'ap-ssid-example', - 'profile.1.network.1.priority': 100, - 'profile.1.network.1.key_mgmt.1.name': 'WPA-PSK', 'profile.1.network.2.key_mgmt.1.name': 'NONE', 'profile.1.network.2.priority': 2, 'profile.1.network.2.status': 'disabled', @@ -122,7 +122,7 @@ def test_wpa2_personal(self): ] self.assertEqualConfig(o.intermediate_data['wpasupplicant'], expected) - def test_wpa2_enterprise(self): + def test_eap_wpa2_enterprise(self): o = self.backend({ "interfaces": [ { @@ -154,6 +154,7 @@ def test_wpa2_enterprise(self): 'device.1.profile': 'AUTO', 'device.1.status': 'enabled', 'profile.1.name': 'AUTO', + 'profile.1.network.1.bssid': '00:11:22:33:44:55', 'profile.1.network.1.eap.1.name': 'TTLS', 'profile.1.network.1.eap.1.status': 'enabled', 'profile.1.network.1.identity': 'definitely-fake-identity', @@ -262,9 +263,9 @@ def test_no_encryption(self): 'device.1.profile': 'AUTO', 'device.1.status': 'enabled', 'profile.1.name': 'AUTO', + 'profile.1.network.1.key_mgmt.1.name': 'NONE', 'profile.1.network.1.priority': 100, 'profile.1.network.1.ssid': 'ap-ssid-example', - 'profile.1.network.1.key_mgmt.1.name': 'NONE', 'profile.1.network.2.key_mgmt.1.name': 'NONE', 'profile.1.network.2.priority': 2, 'profile.1.network.2.status': 'disabled', @@ -304,10 +305,10 @@ def test_wpa2_personal(self): 'device.1.profile': 'AUTO', 'device.1.status': 'disabled', 'profile.1.name': 'AUTO', + 'profile.1.network.1.key_mgmt.1.name': 'NONE', 'profile.1.network.1.priority': 100, - 'profile.1.network.1.ssid': 'ap-ssid-example', 'profile.1.network.1.psk': 'cucumber', - 'profile.1.network.1.key_mgmt.1.name': 'NONE', + 'profile.1.network.1.ssid': 'ap-ssid-example', 'profile.1.network.2.key_mgmt.1.name': 'NONE', 'profile.1.network.2.priority': 2, 'profile.1.network.2.status': 'disabled', From 8e2ea8e429e252d71fd07991be1575391223b32a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 16 Aug 2017 12:55:45 +0200 Subject: [PATCH 308/342] [airos] updated wpasupplicant converter --- netjsonconfig/backends/airos/converters.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index b0f92ca48..50027b1c4 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -779,10 +779,6 @@ def _access_point_intermediate(self, original): 'network': [network, self.secondary_network()], } - if proto == 'wpa2_enterprise': - del temp_dev['profile'] - del profile['name'] - result.append({ 'device': [temp_dev], 'profile': [profile], From 80cc19bb82f90792ac3cae6e96460d82383e1a85 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 16 Aug 2017 12:56:06 +0200 Subject: [PATCH 309/342] [airos] fixed keyerror in ebtables converter --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 50027b1c4..c92a85364 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -162,7 +162,7 @@ def vlan(self): """ Return all the vlan interfaces """ - return vlan(get_copy(self.netjson, self.netjson_key, [])) + return vlan(get_copy(self.netjson, 'interfaces', [])) @property def wireless(self): From 4be48c5cb1716b5d53e7a7d54d25518ca1938049 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 16 Aug 2017 17:15:55 +0200 Subject: [PATCH 310/342] [airos] removed configuration for channel width channel width can be set from netjson but it also change the ieee_mode configuration value. As I can't still pinpoint this relation chabw is set to two default values, one for station and one for access point --- netjsonconfig/backends/airos/radio.py | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/netjsonconfig/backends/airos/radio.py b/netjsonconfig/backends/airos/radio.py index 33fb1f75b..12b398587 100644 --- a/netjsonconfig/backends/airos/radio.py +++ b/netjsonconfig/backends/airos/radio.py @@ -1,3 +1,6 @@ +""" +This configuration is for a radio in a ``station`` device without encryption +""" radio_device_base = { 'ack': {'auto': 'enabled'}, 'ackdistance': 643, @@ -29,7 +32,7 @@ 'freq': 0, 'ieee_mode': 'auto', 'low_txpower_mode': 'disabled', - 'mode': 'managed', # ap => master, sta => managed + 'mode': 'managed', 'obey': 'enabled', 'polling': 'enabled', 'polling_11ac_11n_compat': 0, @@ -54,20 +57,6 @@ } -def channel_to_mode(channel): - """ - Returns the ``ieee_mode`` value from the channel width - """ - mapping = { - 10: '11acvht20', - 20: '11acvht20', - 40: '11acvht40', - 60: '11acvht40', - 80: '11acvht80', - } - return mapping[channel] - - def access_point(radio): """ Return the configuration for a radio device whose wireless @@ -76,8 +65,8 @@ def access_point(radio): base = radio_device_base.copy() base.update({ 'devname': radio['name'], - 'chanbw': radio['channel_width'], - 'ieee_mode': channel_to_mode(radio['channel_width']), + 'chanbw': 80, + 'ieee_mode': '11acvht80', 'mode': 'master', }) return base @@ -91,7 +80,7 @@ def station(radio): base = radio_device_base.copy() base.update({ 'devname': radio['name'], - 'chanbw': radio['channel_width'], + 'chanbw': 0, 'txpower': radio.get('tx_power', 24), }) return base From 57aea99352a33f6e1af43ff89c6e5033f1c6649e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 17 Aug 2017 13:44:52 +0200 Subject: [PATCH 311/342] [airos] changed from numerals to string in radio module and updated tests to pass with the new defaults --- netjsonconfig/backends/airos/radio.py | 2 +- tests/airos/test_radio.py | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/netjsonconfig/backends/airos/radio.py b/netjsonconfig/backends/airos/radio.py index 12b398587..c3e8dbd75 100644 --- a/netjsonconfig/backends/airos/radio.py +++ b/netjsonconfig/backends/airos/radio.py @@ -47,7 +47,7 @@ 'scan_list': {'status': 'disabled'}, 'scanbw': {'status': 'disabled'}, 'status': 'enabled', # cannot disable - 'subsystemid': 0xe7f5, + 'subsystemid': '0xe7f5', 'txpower': 24, } diff --git a/tests/airos/test_radio.py b/tests/airos/test_radio.py index fcbe34e3c..9456f89e6 100644 --- a/tests/airos/test_radio.py +++ b/tests/airos/test_radio.py @@ -1,3 +1,5 @@ +from unittest import skip + from .mock import ConverterTest, RadioAirOs @@ -63,7 +65,7 @@ def test_active_radio(self): '1.atpc.threshold': 36, '1.cable.loss': 0, '1.center.1.freq': 0, - '1.chanbw': 20, + '1.chanbw': 0, '1.cmsbias': 0, '1.countrycode': 380, '1.cwm.enable': 0, @@ -88,7 +90,7 @@ def test_active_radio(self): '1.scan_list.status': 'disabled', '1.scanbw.status': 'disabled', '1.status': 'enabled', - '1.subsystemid': 0xe7f5, + '1.subsystemid': '0xe7f5', '1.txpower': 24, }, { @@ -99,6 +101,7 @@ def test_active_radio(self): self.assertEqualConfig(o.intermediate_data['radio'], expected) + @skip('channel width breaks the fast build script') def test_channel_width(self): """ TODO: channel brandwidth tested only on 802.11ac @@ -168,7 +171,7 @@ def test_channel_width(self): '1.scan_list.status': 'disabled', '1.scanbw.status': 'disabled', '1.status': 'enabled', - '1.subsystemid': 0xe7f5, + '1.subsystemid': '0xe7f5', '1.txpower': 24, }, { @@ -208,7 +211,7 @@ def test_active_radio(self): { 'name': 'ath0', 'channel': 36, - 'channel_width': 20, + 'channel_width': 80, 'protocol': '802.11ac', } ] @@ -228,7 +231,7 @@ def test_active_radio(self): '1.atpc.threshold': 36, '1.cable.loss': 0, '1.center.1.freq': 0, - '1.chanbw': 20, + '1.chanbw': 80, '1.cmsbias': 0, '1.countrycode': 380, '1.cwm.enable': 0, @@ -236,7 +239,7 @@ def test_active_radio(self): '1.devname': 'ath0', '1.dfs.status': 'enabled', '1.freq': 0, - '1.ieee_mode': '11acvht20', + '1.ieee_mode': '11acvht80', '1.low_txpower_mode': 'disabled', '1.mode': 'master', '1.obey': 'enabled', @@ -253,7 +256,7 @@ def test_active_radio(self): '1.scan_list.status': 'disabled', '1.scanbw.status': 'disabled', '1.status': 'enabled', - '1.subsystemid': 0xe7f5, + '1.subsystemid': '0xe7f5', '1.txpower': 24, }, { From 5a693f3af7cb1f00600b50bb2ce591ce36aed1b9 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 11:45:20 +0200 Subject: [PATCH 312/342] [airos] expanded docstring for status_from_interface --- netjsonconfig/backends/airos/aaa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/aaa.py b/netjsonconfig/backends/airos/aaa.py index d736b435d..3f86375c0 100644 --- a/netjsonconfig/backends/airos/aaa.py +++ b/netjsonconfig/backends/airos/aaa.py @@ -130,7 +130,7 @@ def profile_from_interface(interface): def status_from_interface(interface): """ - Returns ``aaa.status`` from interface + Returns ``aaa.status`` and ``aaa.1.status`` from interface """ status = _status.copy() status.update( From 9209346370dd6fc7bd99b0fa42958bef866b73cb Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 12:16:07 +0200 Subject: [PATCH 313/342] [airos] added information about the radio converter --- docs/source/backends/airos.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index a38f3e32c..44f1dffe4 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -203,6 +203,13 @@ For the lazy one we provide these defaults } } +Radio +----- + +The following properties of a ``Radio Object`` are used during the conversion, the others have been set to safe defaults. + +* ``name`` + Ssh --- From a29921407cf261c869ce6e8f088520b35c5af5fd Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 12:55:20 +0200 Subject: [PATCH 314/342] [airos] removed netjson_key=general attribute from converters --- netjsonconfig/backends/airos/converters.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index c92a85364..90cf6d7c8 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -36,7 +36,6 @@ def netmode(self): class Aaa(AirOsConverter): - netjson_key = 'general' @property def bridge(self): @@ -108,7 +107,6 @@ def to_intermediate(self): class Discovery(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): result = [ @@ -147,7 +145,6 @@ def to_intermediate(self): class Dyndns(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): result = [{'status': 'disabled'}] @@ -155,7 +152,6 @@ def to_intermediate(self): class Ebtables(AirOsConverter): - netjson_key = 'general' @property def vlan(self): @@ -227,7 +223,6 @@ def to_intermediate(self): class Httpd(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): result = [ @@ -247,7 +242,6 @@ def to_intermediate(self): class Igmpproxy(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): result = {'status': 'disabled'} @@ -257,7 +251,6 @@ def to_intermediate(self): class Iptables(AirOsConverter): - netjson_key = 'general' _base = { 'sys': { @@ -387,7 +380,6 @@ def to_intermediate(self): class Pwdog(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): result = [] @@ -557,7 +549,6 @@ def to_key(x): class Syslog(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): result = [] @@ -573,7 +564,6 @@ def to_intermediate(self): class System(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): result = [] @@ -598,7 +588,6 @@ def to_intermediate(self): class Telnetd(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): result = [] @@ -610,21 +599,18 @@ def to_intermediate(self): class Tshaper(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): return (('tshaper', [{'status': 'disabled'}]),) class Unms(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): return (('unms', [{'status': 'disabled'}]),) class Update(AirOsConverter): - netjson_key = 'general' def to_intermediate(self): result = [] From 455472d9fb75751df88e9d543ee980b1c16d97bf Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 13:14:18 +0200 Subject: [PATCH 315/342] [airos] added list of backend that have defaults to docs --- docs/source/backends/airos.rst | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 44f1dffe4..083183abe 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -62,6 +62,43 @@ Extending the backend Please see the :ref:`airos-intermediate-representation` page for extending converters and adding functionalities to this backend +Converters with defaults +------------------------ + +NetSJON does not map explicitly to various section of the AirOS device configuration. For those section we have provided default values that should work both in ``bridge`` and ``router`` mode. + +The list of "defaulted" converters follows: + +* Discovery +* Dhcpc + + * ``dhcpc.devname`` defaults to ``br0`` + +* Dyndns +* Httpd +* Igmpproxy +* Iptables + + * ``iptables.sys.mgmt.devname`` defaults to ``br0`` + +* Netconf + + * the first interface with a ``gateway`` specified is the management interface in ``bridge`` mode + * the first interface with a ``gateway`` specified is the ``wan`` interface in ``router`` mode + +* Pwdog +* Radio + + * most of the configuration for the radio interface is taken from a PowerBeam ``PBE-5AC-400`` + +* Syslog +* System +* Telnetd +* Tshaper +* Unms +* Update +* Upnpd + General settings ---------------- From 4026a5870192b114d66ed87fd4010fe0d8391983 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 13:15:05 +0200 Subject: [PATCH 316/342] [airos][doc] make etherenet a subsection of interfaces --- docs/source/backends/airos.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 083183abe..2a2c972b8 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -152,7 +152,7 @@ As an example here is a snippet that set the vlan ``eth0.2`` to be the managemen } Ethernet -======== +^^^^^^^^ The ``ethernet`` interface can be configured to allow auto-negotiation and flow control with the properties ``autoneg`` and ``flowcontrol`` From 60e843417f03013840e9c40715c294f6378c2a47 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 13:15:51 +0200 Subject: [PATCH 317/342] [airos][doc] fixed typo in hashed password representation --- docs/source/backends/airos.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 2a2c972b8..f7fd1dd43 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -297,7 +297,7 @@ From the antenna configuration take the user section. users.1.name=ubnt users.1.password=$1$yRo1tmtC$EcdoRX.JnD4VaEYgghgWg1 -In the line ``users.1.password=$1$yRo1tmtC$EcdoRX.JnD4VaEYgghgWg1`` there are both the salt and the password hash in the format ``$ algorithm $ salt $ hash $``, e.g in the previous block ``algorithm=1``, ``salt=yRo1tmtC`` and ``hash=EcdoRX.JnD4VaEYgghgWg1``. +In the line ``users.1.password=$1$yRo1tmtC$EcdoRX.JnD4VaEYgghgWg1`` there are both the salt and the password hash in the format ``$ algorithm $ salt $ hash``, e.g in the previous block ``algorithm=1``, ``salt=yRo1tmtC`` and ``hash=EcdoRX.JnD4VaEYgghgWg1``. To specify the password in NetJSON use the ``user`` property. From a1d190bb46bf9dee42bd854bd5373b92222ce0df Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 13:16:54 +0200 Subject: [PATCH 318/342] [airos][doc] add role section to interface --- docs/source/backends/airos.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index f7fd1dd43..8458a9cb7 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -171,6 +171,11 @@ As an example here is a snippet that enables both auto-negotiation and flow cont ] } +Role +^^^^ + +Interfaces can be assigned a ``role`` to mimic the web interfaces features. As an example setting the ``management`` property of an address to ``true`` will add the role ``mlan`` to the interface configuration. If not set the management interface will be selected as the (first) one providing a gateway in one of it's addresses. + DNS servers ----------- From df89b85c89011644fd9a70190b163c9b1ab5fd12 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 13:20:00 +0200 Subject: [PATCH 319/342] [airos] added logic and test for iptables converter --- netjsonconfig/backends/airos/converters.py | 11 ++++-- tests/airos/test_iptables.py | 41 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 tests/airos/test_iptables.py diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 90cf6d7c8..81beada26 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -260,17 +260,22 @@ class Iptables(AirOsConverter): } _status = { - 'status': 'enabled', + 'bridge': { + 'status': 'disabled', + }, + 'router': { + 'status': 'enabled', + } } def bridge_intermediate(self): base = self._base.copy() - iptables_status = self._status.copy() + iptables_status = self._status['bridge'].copy() return [iptables_status, base] def router_intermediate(self): base = self._base.copy() - iptables_status = self._status.copy() + iptables_status = self._status['router'].copy() base['sys'].update({ 'fw': {'status': 'disabled'}, 'mgmt': [ diff --git a/tests/airos/test_iptables.py b/tests/airos/test_iptables.py new file mode 100644 index 000000000..9f4106052 --- /dev/null +++ b/tests/airos/test_iptables.py @@ -0,0 +1,41 @@ +from .mock import ConverterTest, IptablesAirOs + + +class IptablesConverter(ConverterTest): + backend = IptablesAirOs + + def test_bridge(self): + o = self.backend({ + 'netmode': 'bridge', + }) + o.to_intermediate() + expected = [ + { + 'status': 'disabled', + }, + { + 'sys.portfw.status': 'disabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['iptables'], expected) + + def test_router(self): + o = self.backend({ + 'netmode': 'router', + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + 'sys.portfw.status': 'disabled', + 'sys.fw.status': 'disabled', + 'sys.mgmt.1.devname': 'br0', + 'sys.mgmt.1.status': 'enabled', + 'sys.mgmt.status': 'enabled', + 'sys.status': 'enabled', + }, + ] + self.assertEqualConfig(o.intermediate_data['iptables'], expected) From 7d375e62bbeeb94d99832ec61b91e31d45faf9b5 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 14:06:31 +0200 Subject: [PATCH 320/342] [airos][doc] fixed typos --- docs/source/backends/airos.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 8458a9cb7..1af284f81 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -102,7 +102,7 @@ The list of "defaulted" converters follows: General settings ---------------- -From the ``general`` key we can configure the contact and the location for a device using the ``contact`` and ``location`` properties. +From the ``general`` property we can configure the contact and the location for a device using the ``contact`` and ``location`` properties. The following snippet specify both contact and location: @@ -112,7 +112,7 @@ The following snippet specify both contact and location: "type": "DeviceConfiguration", "general": { "contact": "user@example.com", - "location": "Up in the roof" + "location": "Up on the roof" } } @@ -186,7 +186,7 @@ GUI As an extension to `NetJSON ` you can use the ``gui`` key to set the language of the interface -The default values for this key are as reported below +The default values for this key are reported below .. code-block:: json From 8fd8b608b45ccdf99825c5693a40aba14a9a90d4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 14:07:51 +0200 Subject: [PATCH 321/342] [airos] updated ap_eap function to rely on ap_psk keys --- netjsonconfig/backends/airos/aaa.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/netjsonconfig/backends/airos/aaa.py b/netjsonconfig/backends/airos/aaa.py index 3f86375c0..f85042426 100644 --- a/netjsonconfig/backends/airos/aaa.py +++ b/netjsonconfig/backends/airos/aaa.py @@ -33,16 +33,15 @@ def ap_eap(interface): Return the configuration for ``aaa`` when in ``access_point`` mode with eap authentication """ - return { - 'devname': radio(interface), - 'driver': 'madwifi', - 'ssid': ssid(interface), + base = ap_psk(interface) + base.update({ 'wpa': { '1.pairwise': 'CCMP', 'key': [{'mgmt': 'WPA-EAP'}], 'mode': 2, }, - } + }) + return base def sta_none(interface): From fcb212c75ac2d2f7f5d113e270725f5a910de9f4 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 19:02:43 +0200 Subject: [PATCH 322/342] [airos] removed ap key from base device --- netjsonconfig/backends/airos/wireless.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netjsonconfig/backends/airos/wireless.py b/netjsonconfig/backends/airos/wireless.py index 2ab44fec9..153b7817e 100644 --- a/netjsonconfig/backends/airos/wireless.py +++ b/netjsonconfig/backends/airos/wireless.py @@ -21,7 +21,6 @@ 'signal_led4': 15, 'signal_led_status': 'enabled', 'ssid': '', - 'ap': '', 'status': '', 'wds': {'status': 'enabled'}, } From 781682c9d16bccd0720c00a7ea8e77ce6631230b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 19:04:24 +0200 Subject: [PATCH 323/342] [airos] updated test for wireless station --- tests/airos/test_wireless.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/airos/test_wireless.py b/tests/airos/test_wireless.py index bcb74303f..4c443853e 100644 --- a/tests/airos/test_wireless.py +++ b/tests/airos/test_wireless.py @@ -156,6 +156,7 @@ def test_active_wireless(self): expected = [ { '1.addmtikie': 'enabled', + '1.ap': '00:11:22:33:44:55', '1.devname': 'radio0', '1.hide_ssid': 'disabled', '1.l2_isolation': 'disabled', @@ -215,6 +216,7 @@ def test_inactive_wireless(self): expected = [ { '1.addmtikie': 'enabled', + '1.ap': '00:11:22:33:44:55', '1.devname': 'radio0', '1.hide_ssid': 'disabled', '1.l2_isolation': 'disabled', From 98f821ce7d70374a182f0e671d3f142703a84433 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 Aug 2017 19:17:45 +0200 Subject: [PATCH 324/342] [airos] make wonderful warning and links --- docs/source/backends/airos.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 1af284f81..e207ccb5d 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -7,7 +7,13 @@ AirOS Backend The ``AirOs`` backend allows to generate AirOS v8.3 compatible configurations. .. warning:: - This backend is experimental, please read this document carefully to prevent misconfiguration problems + + This backend is in experimental stage: it may have bugs and it will + receive backward incompatible updates in during the first 6 months + of development (starting from September 2017). + Early feedback and contributions are very welcome and will help + to stabilize the backend faster. + Initialization @@ -184,7 +190,7 @@ DNS servers GUI --- -As an extension to `NetJSON ` you can use the ``gui`` key to set the language of the interface +As an extension to `NetJSON `_ you can use the ``gui`` key to set the language of the interface The default values for this key are reported below @@ -212,7 +218,7 @@ AirOS v8.3 can operate in ``bridge`` and ``router`` mode (but defaults to ``brid NTP servers ----------- -This is an extension to the `NetJSON` specification. +This is an extension to the `NetJSON `_ specification. By setting the key ``ntp`` property in your input you can provide the configuration for the ntp client running on the device. From 7159c113db756b940e1d707d2ee2f6337996f894 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 21 Aug 2017 11:31:10 +0200 Subject: [PATCH 325/342] [airos] added draft for upgrade process in docs --- docs/source/backends/airos-upgrade.rst | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/source/backends/airos-upgrade.rst diff --git a/docs/source/backends/airos-upgrade.rst b/docs/source/backends/airos-upgrade.rst new file mode 100644 index 000000000..d25888f16 --- /dev/null +++ b/docs/source/backends/airos-upgrade.rst @@ -0,0 +1,47 @@ +.. _airos-configuration-upgrade: + +Tools +----- + +AirOS is shipped with proprietary tools that can parse the configuration file and upgrade the antenna. + +cfgmtd +^^^^^^ + +This tool can write and read data to the memory that persist between reboots. + +ubntcfg +^^^^^^^ + +This tool can parse the configuration and creates the init scripts that configure the device + +rc scripts +^^^^^^^^^^ + +This are not commands but a collection of scripts that orchestrate the configuration process. As they are stored on the antenna they can be modified to obtain different behaviours. + +* update scripts are stored in `/usr/local/rc.d` +* module list is stored in `/etc/startup.list` + +The update process is orchestrated by the `/usr/local/rc.d/rc.do.softrestart` script. + +Process +------- + +AirOS mantains the device configuration in two files, both can be found in `/tmp`. + +* `/tmp/system.cfg` the target configuration +* `/tmp/running.cfg` the running configuration + +If we want to upgrade the device configuration with our file we can overwrite the target configuration and runt the commands `cfgmtd -w` and `/usr/local/rc.d/rc.do.softrestart save` + + +Full transcript of the update processs + +.. code-block:: bash + + cp /path/to/my/config.cfg /tmp/system.cfg + # writes the configuration to the persistent memory + cfgmtd -w /tmp/system.cfg + # initiate the configuration update + /usr/local/rc.d/rc.do.softrestart save From 8ce2c4c0f47ec1d28959aac988e8fb9b4e66bc45 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 21 Aug 2017 11:56:58 +0200 Subject: [PATCH 326/342] [airos] change management property to role enum --- netjsonconfig/backends/airos/schema.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/netjsonconfig/backends/airos/schema.py b/netjsonconfig/backends/airos/schema.py index 3e9b27e25..a572c994e 100644 --- a/netjsonconfig/backends/airos/schema.py +++ b/netjsonconfig/backends/airos/schema.py @@ -28,12 +28,25 @@ "definitions": { "base_address": { "properties": { - "management": { - "type": "boolean", - "default": False, - "title": "Management", - "description": "Management interface", - "format": "checkbox", + "role": { + "type": "string", + "enum": [ + "none", + "mlan", + "wan", + "lan", + ], + "options": { + "enum_titles": [ + "None", + "Management interface (bridge mode)", + "Wan interface (router mode)", + "Lan interface (router mode)", + ] + }, + "default": "none", + "title": "Role", + "description": "Interface role", "propertyOrder": 0, } } From 3b3241844ba74892b2a7797fcbd435f8717494f9 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 21 Aug 2017 12:49:25 +0200 Subject: [PATCH 327/342] [airos] move toctree for additional airos pages --- docs/source/backends/airos.rst | 3 +++ docs/source/index.rst | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index e207ccb5d..e890b55ed 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -14,7 +14,10 @@ The ``AirOs`` backend allows to generate AirOS v8.3 compatible configurations. Early feedback and contributions are very welcome and will help to stabilize the backend faster. +.. toctree:: + intermediate + airos-upgrade Initialization -------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index 304dff4ba..d8637a62d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -49,7 +49,6 @@ Contents: /general/setup /general/basics /backends/airos - /backends/intermediate /backends/openwrt /backends/openwisp /backends/openvpn From 5a727e4b64ca09b5a050fc2edac4ec4869e154a3 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 21 Aug 2017 12:51:27 +0200 Subject: [PATCH 328/342] [airos] added reference to configuration update process --- docs/source/backends/airos.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index e890b55ed..a00b8288d 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -71,6 +71,11 @@ Extending the backend Please see the :ref:`airos-intermediate-representation` page for extending converters and adding functionalities to this backend +The configuration upgrade process +--------------------------------- + +Please see the :ref:`airos-configuration-upgrade` page for information about the process and tools that upgrades the configuration on the device + Converters with defaults ------------------------ From c92cbf090179ec693e1b71660b0ec6f7f4e0cd9b Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 21 Aug 2017 12:53:37 +0200 Subject: [PATCH 329/342] [airos] use role provided from users instead of guess updated docs and converter to use properties role instread of management --- docs/source/backends/airos.rst | 39 ++++++++++++++++++++-- netjsonconfig/backends/airos/converters.py | 11 ++---- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index a00b8288d..bced5a9a8 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -141,7 +141,7 @@ AirOS supports the following types of interfaces * **wirelesss interfaces**: must be of type ``wireless`` * **bridge interfaces**: must be of type ``bridge`` -A network interface can be designed to be the management interfaces by setting the ``managed`` key to ``True`` on the address chosen. +A network interface can be designed to be the management interfaces by setting the ``role`` key to ``mlan`` on the address chosen. As an example here is a snippet that set the vlan ``eth0.2`` to be the management interface on the address ``192.168.1.20`` @@ -156,7 +156,7 @@ As an example here is a snippet that set the vlan ``eth0.2`` to be the managemen { "address": "192.168.1.20", "family": "ipv4", - "managed": true, + "role": "mlan", "mask": 24, "proto": "static" } @@ -188,7 +188,40 @@ As an example here is a snippet that enables both auto-negotiation and flow cont Role ^^^^ -Interfaces can be assigned a ``role`` to mimic the web interfaces features. As an example setting the ``management`` property of an address to ``true`` will add the role ``mlan`` to the interface configuration. If not set the management interface will be selected as the (first) one providing a gateway in one of it's addresses. +Interfaces can be assigned a ``role`` to mimic the web interfaces features. + +As an example setting the ``role`` property of an address to ``mlan`` will add the role ``mlan`` to the interface configuration. + +Here is the snippet to set the role to ``mlan`` + +.. code-block:: json + + { + "interfaces": [ + { + "type": "ethernet", + "name": "eth0", + "addresses": [ + { + "family": "ipv4", + "proto": "static", + "address": "192.168.1.1", + "role": "mlan" + } + ] + } + ] + } + + +This is the list of roles available for a device in ``bridge`` mode: + +* ``mlan`` for the management interface + +This is the list of roles available for a device in ``router`` mode: + +* ``wan`` for the wan interface +* ``lan`` for the lan interface DNS servers diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 81beada26..8e312dcca 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -297,13 +297,6 @@ def to_intermediate(self): class Netconf(AirOsConverter): netjson_key = 'interfaces' - def type_to_role(self, typestr): - roles = { - 'ethernet': 'mlan', - 'bridge': 'mlan', - } - return roles.get(typestr, '') - def to_intermediate(self): result = [] interfaces = [] @@ -331,8 +324,8 @@ def to_intermediate(self): # configuration for addr in addresses: temp = deepcopy(base) - if addr.get('management'): - temp['role'] = self.type_to_role(interface['type']) + if 'role' in addr: + temp['role'] = addr.get('role', '') # handle explicit address policy if addr['proto'] == 'dhcp': temp['autoip'] = {'status': 'enabled'} From ab5a1298437c8a7fa284f6c830feb6bd9926d4eb Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 21 Aug 2017 13:00:05 +0200 Subject: [PATCH 330/342] [airos] updated test for management interface to use role property --- tests/airos/test_netconf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/airos/test_netconf.py b/tests/airos/test_netconf.py index 553969180..d01a567fa 100644 --- a/tests/airos/test_netconf.py +++ b/tests/airos/test_netconf.py @@ -112,7 +112,7 @@ def test_management_vlan(self): { 'address': '192.168.1.20', 'family': 'ipv4', - 'management': True, + 'role': 'mlan', 'mask': 24, 'proto': 'static', } From 14a904f2489a1314dc1717e807e4141d65cd7195 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 21 Aug 2017 13:16:50 +0200 Subject: [PATCH 331/342] [airos] added warning about absence of management interface also added clarification about mlan -> management lan --- docs/source/backends/airos.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index bced5a9a8..bd27b5f6f 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -190,7 +190,11 @@ Role Interfaces can be assigned a ``role`` to mimic the web interfaces features. -As an example setting the ``role`` property of an address to ``mlan`` will add the role ``mlan`` to the interface configuration. +As an example setting the ``role`` property of an address to ``mlan`` will add the role ``mlan`` to the interface configuration and set it as the management interface. + +.. warning:: + + Not setting a management interface will lock you out from the web interface Here is the snippet to set the role to ``mlan`` From 2b7632b378e8b28f282dde4c4e9ce58dac95b62f Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 21 Aug 2017 13:23:52 +0200 Subject: [PATCH 332/342] [airos] added test for dns servers list --- tests/airos/test_resolv.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/airos/test_resolv.py b/tests/airos/test_resolv.py index e837402ac..9f3172c3a 100644 --- a/tests/airos/test_resolv.py +++ b/tests/airos/test_resolv.py @@ -44,3 +44,26 @@ def test_no_dns_server(self): ] self.assertEqualConfig(o.intermediate_data['resolv'], expected) + + def test_dns_server(self): + o = self.backend({ + "dns_servers": [ + "192.168.1.1" + ], + }) + o.to_intermediate() + expected = [ + { + 'host.1.name': 'airos', + 'host.1.status': 'enabled', + }, + { + 'nameserver.1.ip': '192.168.1.1', + 'nameserver.1.status': 'enabled' + }, + { + 'status': 'enabled', + }, + ] + + self.assertEqualConfig(o.intermediate_data['resolv'], expected) From c11ddc507ebd26e1e883840de58c01fe5387c28a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 21 Aug 2017 13:30:20 +0200 Subject: [PATCH 333/342] [airos] removed dns server section from airos docs as it is standard --- docs/source/backends/airos.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index bd27b5f6f..70c201a22 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -228,10 +228,6 @@ This is the list of roles available for a device in ``router`` mode: * ``lan`` for the lan interface -DNS servers ------------ - - GUI --- From be01dd3ef8aeae985c45b0058497da7d3c749a0f Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 23 Aug 2017 13:11:29 +0200 Subject: [PATCH 334/342] [airos] fixed typo --- docs/source/backends/airos.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/backends/airos.rst b/docs/source/backends/airos.rst index 70c201a22..7b67a2c7e 100644 --- a/docs/source/backends/airos.rst +++ b/docs/source/backends/airos.rst @@ -9,7 +9,7 @@ The ``AirOs`` backend allows to generate AirOS v8.3 compatible configurations. .. warning:: This backend is in experimental stage: it may have bugs and it will - receive backward incompatible updates in during the first 6 months + receive backward incompatible updates during the first 6 months of development (starting from September 2017). Early feedback and contributions are very welcome and will help to stabilize the backend faster. From ae39aad92e846cd92d39b890cb97bd73f0cd047a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Tue, 29 Aug 2017 16:35:54 +0200 Subject: [PATCH 335/342] [airos] fixed ip_interface text input --- netjsonconfig/backends/airos/converters.py | 4 +++- netjsonconfig/backends/airos/interface.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 8e312dcca..19d27d544 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -1,6 +1,8 @@ from copy import deepcopy from ipaddress import ip_interface +import six + from ...utils import get_copy from ..base.converter import BaseConverter from .aaa import bridge_devname, profile_from_interface, status_from_interface @@ -479,7 +481,7 @@ def to_intermediate(self): routes = self.default_routes() original = get_copy(self.netjson, self.netjson_key, []) for r in original: - network = ip_interface(r['destination']) + network = ip_interface(six.text_type(r['destination'])) temp = {} temp['ip'] = str(network.ip) temp['netmask'] = str(network.netmask) diff --git a/netjsonconfig/backends/airos/interface.py b/netjsonconfig/backends/airos/interface.py index 0dc0a7473..66688d291 100644 --- a/netjsonconfig/backends/airos/interface.py +++ b/netjsonconfig/backends/airos/interface.py @@ -1,5 +1,7 @@ from ipaddress import ip_interface +from six import text_type + def autonegotiation(interface): """ @@ -92,7 +94,7 @@ def split_cidr(address): """ Return the address in dict format """ - network = ip_interface('{addr}/{mask}'.format(addr=address['address'], mask=address['mask'])) + network = ip_interface(text_type('{addr}/{mask}'.format(addr=address['address'], mask=address['mask']))) return {'ip': str(network.ip), 'netmask': str(network.netmask)} From 309e712bc2a544ac12c4c6ea2b5e6a0a49994e44 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Thu, 31 Aug 2017 16:39:40 +0200 Subject: [PATCH 336/342] airos] added tests and mock backends for defaulted converters --- tests/airos/mock.py | 30 +++++++++++++++++++++++++- tests/airos/test_dhcpc.py | 2 +- tests/airos/test_snmp.py | 43 +++++++++++++++++++++++++++++++++++++ tests/airos/test_syslog.py | 17 +++++++++++++++ tests/airos/test_system.py | 19 ++++++++++++++++ tests/airos/test_telnetd.py | 16 ++++++++++++++ tests/airos/test_tshaper.py | 11 ++++++++++ tests/airos/test_unms.py | 11 ++++++++++ tests/airos/test_update.py | 13 +++++++++++ tests/airos/test_upnpd.py | 29 +++++++++++++++++++++++++ tests/airos/test_users.py | 31 ++++++++++++++++++++++++++ 11 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 tests/airos/test_snmp.py create mode 100644 tests/airos/test_syslog.py create mode 100644 tests/airos/test_system.py create mode 100644 tests/airos/test_telnetd.py create mode 100644 tests/airos/test_tshaper.py create mode 100644 tests/airos/test_unms.py create mode 100644 tests/airos/test_update.py create mode 100644 tests/airos/test_upnpd.py create mode 100644 tests/airos/test_users.py diff --git a/tests/airos/mock.py b/tests/airos/mock.py index 401e9fba5..e27ab5509 100644 --- a/tests/airos/mock.py +++ b/tests/airos/mock.py @@ -10,7 +10,8 @@ Ntpclient, Pwdog, Radio, Resolv, Route, Snmp, Sshd, Syslog, System, Telnetd, - Update, Users, Vlan, + Tshaper, Unms, Update, + Upnpd, Users, Vlan, Wireless, Wpasupplicant) @@ -237,6 +238,24 @@ class TelnetdAirOs(AirOs): ] +class TshaperAirOs(AirOs): + """ + Mock backend with converter for tshaper + """ + converters = [ + Tshaper, + ] + + +class UnmsAirOs(AirOs): + """ + Mock backend with converter for unms + """ + converters = [ + Unms, + ] + + class UpdateAirOs(AirOs): """ Mock backend with converter for update @@ -246,6 +265,15 @@ class UpdateAirOs(AirOs): ] +class UpnpdAirOs(AirOs): + """ + Mock backend with converter for updnd daemon + """ + converters = [ + Upnpd, + ] + + class UsersAirOs(AirOs): """ Mock backend with converter for users settings diff --git a/tests/airos/test_dhcpc.py b/tests/airos/test_dhcpc.py index 6253d8e9a..298602395 100644 --- a/tests/airos/test_dhcpc.py +++ b/tests/airos/test_dhcpc.py @@ -1,7 +1,7 @@ from .mock import ConverterTest, DhcpcAirOs -class TestNetmodeConverter(ConverterTest): +class TestDhcpcConverter(ConverterTest): backend = DhcpcAirOs diff --git a/tests/airos/test_snmp.py b/tests/airos/test_snmp.py new file mode 100644 index 000000000..090f5323b --- /dev/null +++ b/tests/airos/test_snmp.py @@ -0,0 +1,43 @@ +from .mock import ConverterTest, SnmpAirOs + + +class TestSnmpConverter(ConverterTest): + """ + tests for backends.airos.renderers.SystemRenderer + """ + backend = SnmpAirOs + + def test_defaults(self): + + o = self.backend({}) + o.to_intermediate() + expected = [ + { + 'community': 'public', + 'contact': '', + 'location': '', + 'status': 'enabled', + } + ] + + self.assertEqualConfig(o.intermediate_data['snmp'], expected) + + def test_custom_info(self): + + o = self.backend({ + 'general': { + 'mantainer': 'noone@somedomain.com', + 'location': 'somewhere in the woods', + } + }) + o.to_intermediate() + expected = [ + { + 'community': 'public', + 'contact': 'noone@somedomain.com', + 'location': 'somewhere in the woods', + 'status': 'enabled', + } + ] + + self.assertEqualConfig(o.intermediate_data['snmp'], expected) diff --git a/tests/airos/test_syslog.py b/tests/airos/test_syslog.py new file mode 100644 index 000000000..7b52a8dea --- /dev/null +++ b/tests/airos/test_syslog.py @@ -0,0 +1,17 @@ +from .mock import ConverterTest, SyslogAirOs + + +class TestSyslogConverter(ConverterTest): + backend = SyslogAirOs + + def test_active(self): + o = self.backend({}) + o.to_intermediate() + expected = [ + { + 'remote.port': 514, + 'remote.status': 'disabled', + 'status': 'enabled', + } + ] + self.assertEqualConfig(o.intermediate_data['syslog'], expected) diff --git a/tests/airos/test_system.py b/tests/airos/test_system.py new file mode 100644 index 000000000..c6c1d37c1 --- /dev/null +++ b/tests/airos/test_system.py @@ -0,0 +1,19 @@ +from .mock import ConverterTest, SystemAirOs + + +class TestSystemConverter(ConverterTest): + backend = SystemAirOs + + def test_active(self): + o = self.backend({}) + o.to_intermediate() + expected = [ + { + 'airosx.prov.status': 'enabled', + 'cfg.version': 0, + 'date.status': 'disabled', + 'external.reset': 'enabled', + 'timezone': 'GMT' + } + ] + self.assertEqualConfig(o.intermediate_data['system'], expected) diff --git a/tests/airos/test_telnetd.py b/tests/airos/test_telnetd.py new file mode 100644 index 000000000..5fa8ffb77 --- /dev/null +++ b/tests/airos/test_telnetd.py @@ -0,0 +1,16 @@ +from .mock import ConverterTest, TelnetdAirOs + + +class TestTelnetdConverter(ConverterTest): + backend = TelnetdAirOs + + def test_active(self): + o = self.backend({}) + o.to_intermediate() + expected = [ + { + 'port': 23, + 'status': 'disabled' + } + ] + self.assertEqualConfig(o.intermediate_data['telnetd'], expected) diff --git a/tests/airos/test_tshaper.py b/tests/airos/test_tshaper.py new file mode 100644 index 000000000..5308f0d29 --- /dev/null +++ b/tests/airos/test_tshaper.py @@ -0,0 +1,11 @@ +from .mock import ConverterTest, TshaperAirOs + + +class TestTshaperConverter(ConverterTest): + backend = TshaperAirOs + + def test_active(self): + o = self.backend({}) + o.to_intermediate() + expected = [{'status': 'disabled'}] + self.assertEqualConfig(o.intermediate_data['tshaper'], expected) diff --git a/tests/airos/test_unms.py b/tests/airos/test_unms.py new file mode 100644 index 000000000..6b9812311 --- /dev/null +++ b/tests/airos/test_unms.py @@ -0,0 +1,11 @@ +from .mock import ConverterTest, UnmsAirOs + + +class TestUnmsConverter(ConverterTest): + backend = UnmsAirOs + + def test_active(self): + o = self.backend({}) + o.to_intermediate() + expected = [{'status': 'disabled'}] + self.assertEqualConfig(o.intermediate_data['unms'], expected) diff --git a/tests/airos/test_update.py b/tests/airos/test_update.py new file mode 100644 index 000000000..903952e1d --- /dev/null +++ b/tests/airos/test_update.py @@ -0,0 +1,13 @@ +from .mock import ConverterTest, UpdateAirOs + + +class TestUpdateConverter(ConverterTest): + backend = UpdateAirOs + + def test_status(self): + o = self.backend({}) + o.to_intermediate() + expected = [ + {'check.status': 'enabled'} + ] + self.assertEqualConfig(o.intermediate_data['update'], expected) diff --git a/tests/airos/test_upnpd.py b/tests/airos/test_upnpd.py new file mode 100644 index 000000000..5f8e81c61 --- /dev/null +++ b/tests/airos/test_upnpd.py @@ -0,0 +1,29 @@ +from .mock import ConverterTest, UpnpdAirOs + + +class TestUpnpdConverter(ConverterTest): + """ + tests for backends.airos.renderers.SystemRenderer + """ + backend = UpnpdAirOs + + def test_bridge(self): + + o = self.backend({ + 'netmode': 'bridge', + }) + o.to_intermediate() + with self.assertRaises(KeyError): + o.intermediate_data['upnpd'] + + def test_router(self): + o = self.backend({ + 'netmode': 'router', + }) + o.to_intermediate() + expected = [ + { + 'status': 'disabled', + } + ] + self.assertEqualConfig(o.intermediate_data['upnpd'], expected) diff --git a/tests/airos/test_users.py b/tests/airos/test_users.py new file mode 100644 index 000000000..42af6e879 --- /dev/null +++ b/tests/airos/test_users.py @@ -0,0 +1,31 @@ +from .mock import ConverterTest, UsersAirOs + + +class TestUsersConverter(ConverterTest): + """ + tests for backends.airos.renderers.SystemRenderer + """ + backend = UsersAirOs + + def test_user(self): + + o = self.backend({ + 'user': { + 'name': 'ubnt', + 'password': 'changeme', + 'salt': 'goodsalt', + } + }) + o.to_intermediate() + expected = [ + { + 'status': 'enabled', + }, + { + '1.name': 'ubnt', + '1.password': '$1$goodsalt$changeme', + '1.status': 'enabled', + } + ] + + self.assertEqualConfig(o.intermediate_data['users'], expected) From 6f1777bef82fc6af163223094687894314c2cf5e Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 4 Sep 2017 17:50:07 +0200 Subject: [PATCH 337/342] [airos] handled file creation without tar archive --- netjsonconfig/backends/airos/airos.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/netjsonconfig/backends/airos/airos.py b/netjsonconfig/backends/airos/airos.py index 884d58ff1..04f3340b2 100644 --- a/netjsonconfig/backends/airos/airos.py +++ b/netjsonconfig/backends/airos/airos.py @@ -1,4 +1,6 @@ from collections import OrderedDict +from io import BytesIO +import six from ..base.backend import BaseBackend from .converters import (Aaa, Bridge, Dhcpc, Discovery, Dyndns, Ebtables, Gui, @@ -64,3 +66,22 @@ def to_intermediate(self): super(AirOs, self).to_intermediate() for k, v in self.intermediate_data.items(): self.intermediate_data[k] = to_ordered_list(v) + + def generate(self): + """ + Returns a ``BytesIO`` instance representing the configuration file + + :returns: in-memory configuration file, instance of ``BytesIO`` + """ + fl = BytesIO() + fl.write(six.b(self.render())) + fl.seek(0) + return fl + + def write(self, name, path='./'): + byte_object = self.generate() + file_name = '{0}.cfg'.format(name) + if not path.endswith('/'): + path += '/' + with open('{0}{1}'.format(path, file_name), 'wb') as out: + out.write(byte_object.getvalue()) From 78fdce14b65b41d65cf38e8b8b3aec195f82589d Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 6 Sep 2017 18:45:04 +0200 Subject: [PATCH 338/342] [airos] removed shadowed status function --- netjsonconfig/backends/airos/converters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 19d27d544..76f8c0cf6 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -172,7 +172,7 @@ def wireless(self): @property def ebtables(self): w = self.wireless[0] - status = {'status': 'enabled'} + ebtables_status = {'status': 'enabled'} base = {} if protocol(w) == 'none': base.update(unencrypted(w)) @@ -198,7 +198,7 @@ def ebtables(self): if vlans: base['sys']['vlan.status'] = 'enabled' base['sys']['vlan'] = vlans - return [status, base] + return [ebtables_status, base] def to_intermediate(self): return (('ebtables', self.ebtables),) From c8e3dafcc1574da0c603cec1f10b90e57dcf5119 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 6 Sep 2017 18:45:47 +0200 Subject: [PATCH 339/342] [airos] set default value for sys keyword when not present --- netjsonconfig/backends/airos/converters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 76f8c0cf6..7f791cf51 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -196,6 +196,7 @@ def ebtables(self): }) vlans.append(t) if vlans: + base.setdefault('sys', {}) base['sys']['vlan.status'] = 'enabled' base['sys']['vlan'] = vlans return [ebtables_status, base] From 9a18b3fbd351ac0810f3f9e6f15e552f13411c3a Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Wed, 6 Sep 2017 18:52:40 +0200 Subject: [PATCH 340/342] [cli] print traceback fully if the verbose flag is passed --- bin/netjsonconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/netjsonconfig b/bin/netjsonconfig index 2062eed72..5b09588c2 100644 --- a/bin/netjsonconfig +++ b/bin/netjsonconfig @@ -5,6 +5,7 @@ import sys import six import argparse import netjsonconfig +import traceback description = """ Converts a NetJSON DeviceConfiguration object to native router configurations. @@ -198,5 +199,8 @@ except netjsonconfig.exceptions.ValidationError as e: print(message + info) sys.exit(4) except TypeError as e: + if args.verbose: + traceback.print_exc() + print('netjsonconfig: {0}'.format(e)) sys.exit(5) From e796427531298b751218c9a011ff3466dc27c906 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Mon, 11 Sep 2017 13:33:45 +0200 Subject: [PATCH 341/342] [airos] fixed typo to comply with netjson standard --- netjsonconfig/backends/airos/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netjsonconfig/backends/airos/converters.py b/netjsonconfig/backends/airos/converters.py index 7f791cf51..dc344c9a3 100644 --- a/netjsonconfig/backends/airos/converters.py +++ b/netjsonconfig/backends/airos/converters.py @@ -505,7 +505,7 @@ def to_intermediate(self): result = [ { 'community': 'public', - 'contact': original.get('mantainer', ''), + 'contact': original.get('maintainer', ''), 'location': original.get('location', ''), 'status': 'enabled', }, From 8a41b49f376c230e92f6a82752ccfe57aa59d104 Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Thu, 21 May 2020 20:38:37 -0500 Subject: [PATCH 342/342] [docs] Updated github stars and fork buttons #154 Related to #154 --- docs/source/_github.rst | 6 ------ docs/source/backends/openvpn.rst | 2 +- docs/source/backends/openwisp.rst | 2 +- docs/source/backends/openwrt.rst | 2 +- docs/source/general/basics.rst | 2 +- docs/source/general/commandline_utility.rst | 2 +- docs/source/general/contributing.rst | 2 +- docs/source/general/goals.rst | 2 +- docs/source/general/running_tests.rst | 2 +- docs/source/general/setup.rst | 2 +- docs/source/index.rst | 2 +- 11 files changed, 10 insertions(+), 16 deletions(-) delete mode 100644 docs/source/_github.rst diff --git a/docs/source/_github.rst b/docs/source/_github.rst deleted file mode 100644 index 3f8f505bb..000000000 --- a/docs/source/_github.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. raw:: html - -

- - -

diff --git a/docs/source/backends/openvpn.rst b/docs/source/backends/openvpn.rst index 67fc83cab..2d529b76c 100644 --- a/docs/source/backends/openvpn.rst +++ b/docs/source/backends/openvpn.rst @@ -2,7 +2,7 @@ OpenVPN 2.3 Backend =================== -.. include:: ../_github.rst + The ``OpenVpn`` backend allows to generate OpenVPN 2.3.x compatible configurations. diff --git a/docs/source/backends/openwisp.rst b/docs/source/backends/openwisp.rst index 33ba5805a..9726ff82b 100644 --- a/docs/source/backends/openwisp.rst +++ b/docs/source/backends/openwisp.rst @@ -2,7 +2,7 @@ OpenWISP 1.x Backend ==================== -.. include:: ../_github.rst + The OpenWISP 1.x Backend is based on the OpenWRT backend, therefore it inherits all its features with some differences that are explained in this page. diff --git a/docs/source/backends/openwrt.rst b/docs/source/backends/openwrt.rst index 3ddfacbde..cb1447903 100644 --- a/docs/source/backends/openwrt.rst +++ b/docs/source/backends/openwrt.rst @@ -2,7 +2,7 @@ OpenWRT Backend =============== -.. include:: ../_github.rst + The ``OpenWrt`` backend allows to generate OpenWRT compatible configurations. diff --git a/docs/source/general/basics.rst b/docs/source/general/basics.rst index 902102f59..f403cf3e8 100644 --- a/docs/source/general/basics.rst +++ b/docs/source/general/basics.rst @@ -2,7 +2,7 @@ Basic concepts ============== -.. include:: ../_github.rst + Before starting, let's quickly introduce the main concepts used in netjsonconfig: diff --git a/docs/source/general/commandline_utility.rst b/docs/source/general/commandline_utility.rst index 9e2a9cc08..55e07b98d 100644 --- a/docs/source/general/commandline_utility.rst +++ b/docs/source/general/commandline_utility.rst @@ -2,7 +2,7 @@ Command line utility ==================== -.. include:: ../_github.rst + netjsonconfig ships a command line utility that can be used from the interactive shell, bash scripts or other programming diff --git a/docs/source/general/contributing.rst b/docs/source/general/contributing.rst index 64d5c45c7..fa3461de8 100644 --- a/docs/source/general/contributing.rst +++ b/docs/source/general/contributing.rst @@ -2,7 +2,7 @@ Contributing ============ -.. include:: ../_github.rst + Thank you for taking the time to contribute to netjsonconfig. diff --git a/docs/source/general/goals.rst b/docs/source/general/goals.rst index b75c8caf5..39ba6b8d9 100644 --- a/docs/source/general/goals.rst +++ b/docs/source/general/goals.rst @@ -1,7 +1,7 @@ Motivations and Goals ===================== -.. include:: ../_github.rst + In this page we explain the goals of this project and the motivations that led us on this path. diff --git a/docs/source/general/running_tests.rst b/docs/source/general/running_tests.rst index 5b77e5d46..e2eca8c38 100644 --- a/docs/source/general/running_tests.rst +++ b/docs/source/general/running_tests.rst @@ -2,7 +2,7 @@ Running tests ============= -.. include:: ../_github.rst + Running the test suite is really straightforward! diff --git a/docs/source/general/setup.rst b/docs/source/general/setup.rst index a57caf594..1e046a9e5 100644 --- a/docs/source/general/setup.rst +++ b/docs/source/general/setup.rst @@ -2,7 +2,7 @@ Setup ===== -.. include:: ../_github.rst + Install stable version from pypi -------------------------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index d8637a62d..6f82cb635 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -20,7 +20,7 @@ Netjsonconfig is part of the `OpenWISP project `_. .. image:: ./images/openwisp.org.svg :target: http://openwisp.org -.. include:: _github.rst + **netjsonconfig** is a python library that converts `NetJSON `_ *DeviceConfiguration* objects into real router configurations that can be installed