diff --git a/build-deb/Makefile b/build-deb/Makefile index a6fcaae..b947982 100644 --- a/build-deb/Makefile +++ b/build-deb/Makefile @@ -39,7 +39,7 @@ extract-upstream: tar zxf $(UPSTREAM_ORIG_ARCHIVE) changelog: - dch -v $(VERSION)-$(DEBVERSION) -D $(DIST) -M -u low \ + dch -v $(VERSION) -D $(DIST) -M -u low \ --release-heuristic log clean: diff --git a/build-deb/debian/changelog b/build-deb/debian/changelog index 93cb92f..53a6836 100644 --- a/build-deb/debian/changelog +++ b/build-deb/debian/changelog @@ -1,3 +1,40 @@ +sanji-bundle-route (1.2.0) unstable; urgency=low + + * feat: Support `status` and `wan`. + + -- Aeluin Chen Wed, 05 Apr 2017 16:00:35 +0800 + +sanji-bundle-route (1.1.0) unstable; urgency=low + + * feat: Get interface via shell script command. + + -- Aeluin Chen Thu, 30 Mar 2017 19:18:05 +0800 + +sanji-bundle-route (1.0.3) unstable; urgency=low + + * fix: Add "-pf" for `dhclient`. + * fix: Use "True/False" instead of "1/0". + + -- Aeluin Chen Thu, 09 Mar 2017 18:31:02 +0800 + +sanji-bundle-route (1.0.2) unstable; urgency=low + + * Update pip deps. + + -- Zack YL Shih Thu, 20 Oct 2016 17:38:13 +0800 + +sanji-bundle-route (1.0.1) unstable; urgency=low + + * Remove DNS PUT. + + -- Aeluin Chen Thu, 24 Mar 2016 17:44:02 +0800 + +sanji-bundle-route (1.0.0-1) unstable; urgency=low + + * Update API schema. + + -- Aeluin Chen Wed, 02 Mar 2016 15:36:09 +0800 + sanji-bundle-route (0.10.5-1) unstable; urgency=low * Change dhcp client setting to non-blocking mode. diff --git a/build-deb/debian/source/format b/build-deb/debian/source/format index 163aaf8..89ae9db 100644 --- a/build-deb/debian/source/format +++ b/build-deb/debian/source/format @@ -1 +1 @@ -3.0 (quilt) +3.0 (native) diff --git a/bundle.json b/bundle.json index 5286224..bae0a9c 100644 --- a/bundle.json +++ b/bundle.json @@ -1,9 +1,9 @@ { "name": "route", - "version": "0.10.5", + "version": "1.2.0", "author": "Aeluin Chen", "email": "aeluin.chen@moxa.com", - "description": "Handle the routing table", + "description": "Handles the routing table", "license": "MOXA", "main": "route.py", "argument": "", @@ -17,19 +17,7 @@ "resources": [ { "role": "view", - "resource": "/network/interface" - }, - { - "role": "view", - "resource": "/network/dns" - }, - { - "methods": ["get"], - "resource": "/network/routes/interfaces" - }, - { - "methods": ["get","put"], - "resource": "/network/routes/db" + "resource": "/network/interfaces/:name" }, { "methods": ["get","put"], diff --git a/data/route.json.factory b/data/route.json.factory index f0f970f..8e510da 100644 --- a/data/route.json.factory +++ b/data/route.json.factory @@ -1,3 +1 @@ -{ - "default": "eth0" -} +["eth0"] diff --git a/ip/addr.py b/ip/addr.py index ebf480f..a8e6b40 100755 --- a/ip/addr.py +++ b/ip/addr.py @@ -76,12 +76,12 @@ def ifaddresses(iface): try: info["link"] = open("/sys/class/net/%s/operstate" % iface).read() if "down" == info["link"][:-1]: - info["link"] = 0 + info["link"] = False else: info["link"] = open("/sys/class/net/%s/carrier" % iface).read() - info["link"] = int(info["link"][:-1]) # convert to int + info["link"] = True if int(info["link"][:-1]) == 1 else False except: - info["link"] = 0 + info["link"] = False info["inet"] = [] if netifaces.AF_INET not in full: @@ -118,25 +118,21 @@ def ifupdown(iface, up): def dhclient(iface, enable, script=None): - # Disable the dhcp client and flush interface + # Enable/0Disable the dhcp client and flush interface + # dhclient -pf /var/run/dhclient-.pid + # dhclient -r -pf /var/run/dhclient-.pid + pid_file = "/var/run/dhclient-{}.pid".format(iface) try: - dhclients = sh.awk( - sh.grep( - sh.grep(sh.ps("ax"), iface, _timeout=5), - "dhclient", _timeout=5), - "{print $1}") - dhclients = dhclients.split() - for dhclient in dhclients: - sh.kill(dhclient) + sh.dhclient("-r", "-pf", pid_file, iface) except Exception as e: _logger.info("Failed to stop dhclient: %s" % e) pass if enable: if script: - sh.dhclient("-nw", "-sf", script, iface, _bg=True) + sh.dhclient("-pf", pid_file, "-nw", "-sf", script, iface) else: - sh.dhclient("-nw", iface, _bg=True) + sh.dhclient("-pf", pid_file, "-nw", iface) def ifconfig(iface, dhcpc, ip="", netmask="24", gateway="", script=None): @@ -182,6 +178,7 @@ def ifconfig(iface, dhcpc, ip="", netmask="24", gateway="", script=None): print interfaces() # ifconfig("eth0", True) + # dhclient("eth0", False) # time.sleep(10) # ifconfig("eth1", False, "192.168.31.36") eth0 = ifaddresses("eth0") diff --git a/requirements.txt b/requirements.txt index dd55615..7d5f3fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -paho-mqtt==1.1 +paho-mqtt>=1.1 sh ipcalc netifaces diff --git a/route.py b/route.py index 1ae3057..d8a2076 100755 --- a/route.py +++ b/route.py @@ -11,7 +11,9 @@ from sanji.connection.mqtt import Mqtt from sanji.model_initiator import ModelInitiator from voluptuous import Schema -from voluptuous import Any, Extra, Optional +from voluptuous import Any, Required, Length, REMOVE_EXTRA +import re +import sh import ip @@ -40,22 +42,42 @@ def init(self, *args, **kwargs): except KeyError: self.bundle_env = os.getenv("BUNDLE_ENV", "debug") - path_root = os.path.abspath(os.path.dirname(__file__)) + self._path_root = os.path.abspath(os.path.dirname(__file__)) if self.bundle_env == "debug": # pragma: no cover - path_root = "%s/tests" % path_root + self._path_root = "%s/tests" % self._path_root - self.interfaces = [] + self.interfaces = {} try: - self.load(path_root) + self.load(self._path_root) except: self.stop() raise IOError("Cannot load any configuration.") + # find correct interface if shell command is required + self._cmd_regex = re.compile(r"\$\(([\S\s]+)\)") + self._routes = self._get_routes() + + def _get_routes(self): + routes = [] + for iface in self.model.db: + match = self._cmd_regex.match(iface) + if not match: + routes.append(iface) + continue + try: + with open("{}/iface_cmd.sh".format(self._path_root), "w") as f: + f.write(match.group(1)) + _iface = sh.sh("{}/iface_cmd.sh".format(self._path_root)) + routes.append(str(_iface).rstrip()) + except Exception as e: + _logger.debug(e) + return routes + def run(self): while True: sleep(self.update_interval) try: - self.try_update_default(self.model.db) + self.try_update_default(self._routes) except Exception as e: _logger.debug(e) @@ -97,11 +119,14 @@ def list_interfaces(self): iface_info = ip.addr.ifaddresses(iface) except: continue - if 1 == iface_info["link"]: + if iface_info["link"] is True: inet_ip = [inet["ip"] for inet in iface_info["inet"] if "" != inet["ip"]] - if len(inet_ip): + if len(inet_ip) and \ + (iface in self.interfaces and + self.interfaces[iface]["status"] is True and + self.interfaces[iface]["wan"] is True): data.append(iface) return data @@ -119,6 +144,8 @@ def get_default(self): else: return default + default["wan"] = True + default["status"] = True default["gateway"] = gw[0] default["interface"] = gw[1] return default @@ -132,11 +159,6 @@ def update_wan_info(self, interface): """ self.publish.event.put("/network/wan", data={"interface": interface}) - # TODO: modify DNS to listen `/network/wan` instead - res = self.publish.put("/network/dns", data={"source": interface}) - if res.code != 200: - raise RuntimeWarning(res.data["message"]) - def update_default(self, default): """ Update default gateway. If updated failed, should recover to previous @@ -175,35 +197,31 @@ def _try_update_default(self, routes): Try to update the default gateway. Args: - routes: dict format including default gateway interface and - secondary default gateway interface. + routes: array format of default gateway list with priority. For example: - { - "default": "wwan0", - "secondary": "eth0" - } + ["wwan0", "eth0"] """ ifaces = self.list_interfaces() if not ifaces: + # FIXME: keep or clean? + # self.update_default({}) raise IPRouteError("Interfaces should be UP.") default = {} - if routes["default"] in ifaces: - default["interface"] = routes["default"] - elif routes["secondary"] in ifaces: - default["interface"] = routes["secondary"] + for iface in routes: + if iface in ifaces: + default["interface"] = iface + break else: self.update_default({}) return # find gateway by interface - for iface in self.interfaces: - if iface["interface"] == default["interface"]: - default = iface - break + default.update(self.interfaces[default["interface"]]) current = self.get_default() - if current != default: + if current.get("interface", "") != default.get("interface", "") or \ + current.get("gateway", "") != default.get("gateway", ""): self.update_default(default) def try_update_default(self, routes): @@ -213,7 +231,7 @@ def try_update_default(self, routes): except IPRouteError as e: _logger.debug(e) - def update_router(self, interface): + def update_router(self, iface): """ Save the interface name with its gateway and update the default gateway if needed. @@ -224,46 +242,44 @@ def update_router(self, interface): Args: interface: dict format with interface "name" and/or "gateway". """ + if "status" not in iface: + iface["status"] = True + if "wan" not in iface: + iface["wan"] = True + # update the router information - for iface in self.interfaces: - if iface["interface"] == interface["name"]: - if "gateway" in interface: - iface["gateway"] = interface["gateway"] - break - else: - iface = {} - iface["interface"] = interface["name"] - if "gateway" in interface: - iface["gateway"] = interface["gateway"] - self.interfaces.append(iface) + if iface["name"] not in self.interfaces: + self.interfaces[iface["name"]] = {} + self.interfaces[iface["name"]]["status"] = iface["status"] + self.interfaces[iface["name"]]["wan"] = iface["wan"] + if "gateway" in iface: + self.interfaces[iface["name"]]["gateway"] = iface["gateway"] # check if the default gateway need to be modified - self.try_update_default(self.model.db) + self.try_update_default(self._routes) - def set_default(self, default, is_default=True): + def get_default_routes(self): """ - Update default / secondary gateway. + Get default gateway list. """ - if is_default: - def_type = "default" - else: - def_type = "secondary" + return self._routes + def set_default_routes(self, defaults): + """ + Update default gateway list. + """ # save the setting # if no interface but has gateway, do not update anything - if "interface" in default: - self.model.db[def_type] = default["interface"] - elif "gateway" not in default: - self.model.db[def_type] = "" + self.model.db = defaults self.save() + self._routes = self._get_routes() try: - if is_default: - self.update_default(default) + self.update_default(defaults) except Exception as e: # try database if failed try: - self.try_update_default(self.model.db) + self.try_update_default(self._routes) except IPRouteError as e2: _logger.debug( "Failed to recover the default gateway: {}".format(e2)) @@ -271,49 +287,37 @@ def set_default(self, default, is_default=True): _logger.error(error) raise IPRouteError(error) - @Route(methods="get", resource="/network/routes/interfaces") - def _get_interfaces(self, message, response): - """ - Get available interfaces. - """ - return response(data=self.list_interfaces()) - @Route(methods="get", resource="/network/routes/default") def _get_default(self, message, response): """ - Get default gateway. + Get default gateway and priority list. """ - return response(data=self.get_default()) + data = self.get_default() + if data is None: + data = {} + data["priorityList"] = self.get_default_routes() + return response(data=data) put_default_schema = Schema({ - Optional("interface"): Any(str, unicode), - Extra: object}) + Required("priorityList"): [Any(str, unicode, Length(1, 255))] + }, extra=REMOVE_EXTRA) @Route(methods="put", resource="/network/routes/default") - def _put_default(self, message, response, schema=put_default_schema): + def _put_default_routes(self, message, response, + schema=put_default_schema): """ Update the default gateway, delete default gateway if data is None or empty. """ try: - self.set_default(message.data) + self.set_default_routes(message.data["priorityList"]) except Exception as e: return response(code=404, data={"message": e}) - return response(data=self.get_default()) - @Route(methods="put", resource="/network/routes/secondary") - def _put_secondary(self, message, response, schema=put_default_schema): - """ - Update the secondary default gateway, delete default gateway if data - is None or empty. - """ - try: - self.set_default(message.data, False) - except Exception as e: - return response(code=404, - data={"message": e}) - return response(data=message.data) + data = {} + data["priorityList"] = self.get_default_routes() + return response(data=data) def set_router_db(self, message, response): """ @@ -337,8 +341,9 @@ def _set_router_db(self, message, response): def _get_router_db(self, message, response): return response(data=self.interfaces) - @Route(methods="put", resource="/network/interface") + @Route(methods="put", resource="/network/interfaces/:name") def _event_router_db(self, message): + message.data["name"] = message.param["name"] self.update_router(message.data) diff --git a/schema/index.yaml b/schema/index.yaml new file mode 100644 index 0000000..35b27e7 --- /dev/null +++ b/schema/index.yaml @@ -0,0 +1,91 @@ +swagger: '2.0' +info: + title: IP Route API + description: Handle the routing table + version: "1.0.0" + +schemes: +- http +- https +produces: +- application/json +paths: + /network/routes/default: + get: + summary: Current Default Route and priority list + description: | + The system returns the current default route information and default + route priority list. + responses: + 200: + description: Default Route + schema: + $ref: '#/definitions/DefaultRoute' + examples: + { + "application/json": { + $ref: '#/externalDocs/x-mocks/DefaultRoute' + } + } + put: + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/DefaultRoute' + summary: Update Default Route Setting + description: Update the default route list. + responses: + 200: + description: OK + schema: + $ref: '#/definitions/DefaultRoute' + examples: + { + "application/json": { + $ref: '#/externalDocs/x-mocks/DefaultRoute_Put' + } + } + +definitions: + DefaultRoute: + title: DefaultRoute + required: + - interface + properties: + interface: + type: string + readOnly: true + description: Indicate the interface name (readonly). + gateway: + type: string + pattern: ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + readOnly: true + description: | + Gateway is a router or a proxy server that routes between networks + (readonly). + priorityList: + type: array + items: + description: interface for default route + type: string + pattern: '[A-Za-z0-9_-]+' + minLength: 2 + maxLength: 255 + example: + $ref: '#/externalDocs/x-mocks/DefaultRoute' + +externalDocs: + url: '#' + x-mocks: + DefaultRoute: + { + "interface": "eth0", + "gateway": "192.168.3.254", + "priorityList": ["wwan0", "eth0"] + } + DefaultRoute_Put: + { + "priorityList": ["wwan0", "eth0"] + } diff --git a/schema/network.yaml b/schema/network.yaml new file mode 100644 index 0000000..df9de93 --- /dev/null +++ b/schema/network.yaml @@ -0,0 +1,125 @@ +swagger: '2.0' +info: + title: Network Interface API + description: Handle the network interfaces + version: "0.1.1" + +schemes: + - http + - https +produces: + - application/json +paths: + /network/interfaces/{name}: + parameters: + - name: name + in: path + type: string + required: true + put: + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Interface' + summary: Update Interface Information + description: Update the specified interface. + responses: + 200: + description: This is an event put, no response required. + schema: + $ref: '#/definitions/Interface' + examples: + { + "application/json": { + $ref: '#/externalDocs/x-mocks/Interface' + } + } + + /network/wan: + put: + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/WAN' + summary: Update WAN + description: Update WAN interface + responses: + 200: + description: This is an event put, no response required. + schema: + $ref: '#/definitions/WAN' + examples: + { + "application/json": { + $ref: '#/externalDocs/x-mocks/WAN' + } + } +definitions: + Interface: + title: Interface + required: + - name + properties: + name: + type: string + readOnly: true + description: Indicate the interface name. + ip: + type: string + pattern: ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + description: IP address for the Ethernet interface. + netmask: + type: string + pattern: ^(254|252|248|240|224|192|128)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|240|224|192|128|0) + description: Subnet mask for the Ethernet interface. + subnet: + type: string + pattern: ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + description: | + A subnet (short for "subnetwork") is an identifiably separate part of + an organization's network. + gateway: + type: string + pattern: ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + description: | + Gateway is a router or a proxy server that routes between networks. + dns: + type: array + items: + type: string + pattern: ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + description: | + The Domain Name System (DNS) is a hierarchical distributed naming + system for computers, services, or any resource connected to the + Internet or a private network. + example: + $ref: '#/externalDocs/x-mocks/Interface' + WAN: + title: WAN + required: + - interface + properties: + interface: + type: string + readOnly: true + description: Indicate the interface name. + +externalDocs: + url: '#' + x-mocks: + Interface: + { + "name": "eth0", + "ip": "192.168.3.127", + "netmask": "255.255.255.0", + "subnet": "192.168.3.0", + "gateway": "192.168.3.254" + } + WAN: + { + "interface": "eth0" + } diff --git a/tests/data/route.json.factory b/tests/data/route.json.factory index f0f970f..8e510da 100644 --- a/tests/data/route.json.factory +++ b/tests/data/route.json.factory @@ -1,3 +1 @@ -{ - "default": "eth0" -} +["eth0"] diff --git a/tests/test_route.py b/tests/test_route.py index 54ae998..50ceda1 100644 --- a/tests/test_route.py +++ b/tests/test_route.py @@ -30,7 +30,7 @@ def mock_ip_addr_ifaddresses(iface): if "eth0" == iface: return {"mac": "78:ac:c0:c1:a8:fe", - "link": 1, + "link": True, "inet": [{ "broadcast": "192.168.31.255", "ip": "192.168.31.36", @@ -38,7 +38,7 @@ def mock_ip_addr_ifaddresses(iface): "subnet": "192.168.31.0"}]} elif "eth1" == iface: return {"mac": "78:ac:c0:c1:a8:ff", - "link": 0, + "link": False, "inet": [{ "broadcast": "192.168.41.255", "ip": "192.168.41.37", @@ -46,7 +46,7 @@ def mock_ip_addr_ifaddresses(iface): "subnet": "192.168.41.0"}]} elif "ppp0" == iface: return {"mac": "", - "link": 1, + "link": True, "inet": [{ "broadcast": "192.168.41.255", "ip": "192.168.41.37", @@ -91,7 +91,7 @@ def test__load__current_conf(self): load: load current configuration """ self.bundle.load(dirpath) - self.assertEqual("eth0", self.bundle.model.db["default"]) + self.assertEqual("eth0", self.bundle.model.db[0]) def test__load__backup_conf(self): """ @@ -99,7 +99,7 @@ def test__load__backup_conf(self): """ os.remove("%s/data/%s.json" % (dirpath, self.name)) self.bundle.load(dirpath) - self.assertEqual("eth0", self.bundle.model.db["default"]) + self.assertEqual("eth0", self.bundle.model.db[0]) def test__load__no_conf(self): """ @@ -123,6 +123,15 @@ def test__list_interfaces(self, mock_interfaces, mock_ifaddresses): """ mock_interfaces.return_value = ["eth0", "eth1", "ppp0"] mock_ifaddresses.side_effect = mock_ip_addr_ifaddresses + self.bundle.interfaces = {} + self.bundle.interfaces["eth0"] = { + "status": True, + "wan": True + } + self.bundle.interfaces["ppp0"] = { + "status": True, + "wan": True + } ifaces = self.bundle.list_interfaces() self.assertEqual(2, len(ifaces)) @@ -149,7 +158,7 @@ def test__list_interfaces__failed_get_status(self, mock_interfaces, def mock_ip_addr_ifaddresses_ppp0_failed(iface): if "eth0" == iface: return {"mac": "78:ac:c0:c1:a8:fe", - "link": 1, + "link": True, "inet": [{ "broadcast": "192.168.31.255", "ip": "192.168.31.36", @@ -157,7 +166,7 @@ def mock_ip_addr_ifaddresses_ppp0_failed(iface): "subnet": "192.168.31.0"}]} elif "eth1" == iface: return {"mac": "78:ac:c0:c1:a8:ff", - "link": 0, + "link": False, "inet": [{ "broadcast": "192.168.41.255", "ip": "192.168.41.37", @@ -165,6 +174,10 @@ def mock_ip_addr_ifaddresses_ppp0_failed(iface): "subnet": "192.168.41.0"}]} else: raise ValueError + self.bundle.interfaces["eth0"] = { + "status": True, + "wan": True + } mock_interfaces.return_value = ["eth0", "eth1", "ppp0"] mock_ifaddresses.side_effect = mock_ip_addr_ifaddresses_ppp0_failed @@ -309,23 +322,26 @@ def test__try_update_default__by_default( "gateway": "192.168.4.254" } - self.bundle.interfaces = [ - { - "interface": "eth0", + self.bundle.interfaces = { + "eth0": { + "status": True, + "wan": True, "gateway": "192.168.3.254" }, - { - "interface": "eth1", + "eth1": { + "status": True, + "wan": False, "gateway": "192.168.4.254" } - ] + } - routes = {} - routes["default"] = "eth0" - routes["secondary"] = "eth1" + routes = ["eth0", "eth1"] self.bundle._try_update_default(routes) - mock_update_default.assert_called_once_with(self.bundle.interfaces[0]) + + default = self.bundle.interfaces["eth0"] + default["interface"] = "eth0" + mock_update_default.assert_called_once_with(default) @patch.object(IPRoute, "update_default") @patch.object(IPRoute, "get_default") @@ -344,20 +360,20 @@ def test__try_update_default__by_default_with_current_value( "gateway": "192.168.3.254" } - self.bundle.interfaces = [ - { - "interface": "eth0", + self.bundle.interfaces = { + "eth0": { + "status": True, + "wan": True, "gateway": "192.168.3.254" }, - { - "interface": "eth1", + "eth1": { + "status": True, + "wan": True, "gateway": "192.168.4.254" } - ] + } - routes = {} - routes["default"] = "eth0" - routes["secondary"] = "eth1" + routes = ["eth0", "eth1"] self.bundle._try_update_default(routes) self.assertTrue(not mock_update_default.called) @@ -380,30 +396,33 @@ def test__try_update_default__by_secondary( "gateway": "192.168.4.254" } - self.bundle.interfaces = [ - { - "interface": "eth0", + self.bundle.interfaces = { + "eth0": { + "status": False, + "wan": True, "gateway": "192.168.3.254" }, - { - "interface": "eth1", + "eth1": { + "status": True, + "wan": True, "gateway": "192.168.4.254" }, - { - "interface": "wwan0", + "wwan0": { + "status": True, + "wan": True, "gateway": "192.168.5.254" } - ] + } - routes = {} - routes["default"] = "eth0" - routes["secondary"] = "wwan0" + routes = ["eth0", "wwan0"] # act self.bundle._try_update_default(routes) # assert - mock_update_default.assert_called_once_with(self.bundle.interfaces[2]) + default = self.bundle.interfaces["wwan0"] + default["interface"] = "wwan0" + mock_update_default.assert_called_once_with(default) @patch.object(IPRoute, "update_default") @patch.object(IPRoute, "list_interfaces") @@ -416,9 +435,7 @@ def test__try_update_default__delete( """ mock_list_interfaces.return_value = ["eth1"] - routes = {} - routes["default"] = "wwan0" - routes["secondary"] = "eth0" + routes = ["wwan0", "eth0"] self.bundle._try_update_default(routes) mock_update_default.assert_called_once_with({}) @@ -430,20 +447,41 @@ def test__update_router__update_interface( update_router: update router info by interface """ # arrange - self.bundle.interfaces = [ - {"interface": "eth0", "gateway": "192.168.31.254"}, - {"interface": "eth1", "gateway": "192.168.4.254"}] + self.bundle.interfaces = { + "eth0": { + "status": True, + "wan": True, + "gateway": "192.168.31.254" + }, + "eth1": { + "status": True, + "wan": True, + "gateway": "192.168.4.254" + } + } iface = {"name": "eth1", "gateway": "192.168.41.254"} # act self.bundle.update_router(iface) # assert + eth0 = { + "eth0": { + "status": True, + "wan": True, + "gateway": "192.168.31.254" + } + } + eth1 = { + "eth1": { + "status": True, + "wan": True, + "gateway": "192.168.41.254" + } + } self.assertEqual(2, len(self.bundle.interfaces)) - self.assertIn({"interface": "eth0", "gateway": "192.168.31.254"}, - self.bundle.interfaces) - self.assertIn({"interface": "eth1", "gateway": "192.168.41.254"}, - self.bundle.interfaces) + self.assertEqual(eth0["eth0"], self.bundle.interfaces["eth0"]) + self.assertEqual(eth1["eth1"], self.bundle.interfaces["eth1"]) @patch.object(IPRoute, 'update_default') def test__update_router__add_interface_with_gateway( @@ -452,19 +490,36 @@ def test__update_router__add_interface_with_gateway( update_router: add a new interface with gateway """ # arrange - self.bundle.interfaces = [ - {"interface": "eth0", "gateway": "192.168.31.254"}] + self.bundle.interfaces = { + "eth0": { + "status": True, + "wan": True, + "gateway": "192.168.31.254" + } + } iface = {"name": "eth1", "gateway": "192.168.41.254"} # act self.bundle.update_router(iface) # assert + eth0 = { + "eth0": { + "status": True, + "wan": True, + "gateway": "192.168.31.254" + } + } + eth1 = { + "eth1": { + "status": True, + "wan": True, + "gateway": "192.168.41.254" + } + } self.assertEqual(2, len(self.bundle.interfaces)) - self.assertIn({"interface": "eth0", "gateway": "192.168.31.254"}, - self.bundle.interfaces) - self.assertIn({"interface": "eth1", "gateway": "192.168.41.254"}, - self.bundle.interfaces) + self.assertEqual(eth0["eth0"], self.bundle.interfaces["eth0"]) + self.assertEqual(eth1["eth1"], self.bundle.interfaces["eth1"]) @patch.object(IPRoute, 'update_default') def test__update_router__add_interface_without_gateway( @@ -473,19 +528,35 @@ def test__update_router__add_interface_without_gateway( update_router: add a new interface without gateway """ # arrange - self.bundle.interfaces = [ - {"interface": "eth0", "gateway": "192.168.31.254"}] + self.bundle.interfaces = { + "eth0": { + "status": True, + "wan": True, + "gateway": "192.168.31.254" + } + } iface = {"name": "eth1"} # act self.bundle.update_router(iface) # assert + eth0 = { + "eth0": { + "status": True, + "wan": True, + "gateway": "192.168.31.254" + } + } + eth1 = { + "eth1": { + "status": True, + "wan": True + } + } self.assertEqual(2, len(self.bundle.interfaces)) - self.assertIn({"interface": "eth0", "gateway": "192.168.31.254"}, - self.bundle.interfaces) - self.assertIn({"interface": "eth1"}, - self.bundle.interfaces) + self.assertEqual(eth0["eth0"], self.bundle.interfaces["eth0"]) + self.assertEqual(eth1["eth1"], self.bundle.interfaces["eth1"]) @patch.object(IPRoute, "get_default") @patch.object(IPRoute, 'try_update_default') @@ -499,143 +570,106 @@ def test__update_router__update_default( "interface": "eth0", "gateway": "192.168.3.254" } - self.bundle.interfaces = [ - {"interface": "eth0", "gateway": "192.168.3.254"}, - {"interface": "eth1", "gateway": "192.168.4.254"}] + self.bundle.interfaces = { + "eth0": { + "status": True, + "wan": True, + "gateway": "192.168.3.254" + }, + "eth1": { + "status": True, + "wan": True, + "gateway": "192.168.4.254" + } + } iface = {"name": "eth0", "gateway": "192.168.31.254"} # act self.bundle.update_router(iface) # assert + eth0 = { + "eth0": { + "status": True, + "wan": True, + "gateway": "192.168.31.254" + } + } + eth1 = { + "eth1": { + "status": True, + "wan": True, + "gateway": "192.168.4.254" + } + } self.assertEqual(2, len(self.bundle.interfaces)) - self.assertIn({"interface": "eth0", "gateway": "192.168.31.254"}, - self.bundle.interfaces) - self.assertIn({"interface": "eth1", "gateway": "192.168.4.254"}, - self.bundle.interfaces) + self.assertEqual(eth0["eth0"], self.bundle.interfaces["eth0"]) + self.assertEqual(eth1["eth1"], self.bundle.interfaces["eth1"]) mock_try_update_default.assert_called_once_with(self.bundle.model.db) @patch.object(IPRoute, "update_default") - def test__set_default__default(self, mock_update_default): + def test__set_default_routes__default(self, mock_update_default): """ - set_default: update default gateway + set_default_routes: update default gateway list """ # arrange - self.bundle.model.db["default"] = "eth1" - default = { - "interface": "eth0", - "gateway": "192.168.3.254" - } + self.bundle.model.db = ["eth1", "wwan0"] + default = ["wwan0", "eth0"] # act - self.bundle.set_default(default) + self.bundle.set_default_routes(default) # assert - self.assertEqual(self.bundle.model.db, {"default": "eth0"}) + self.assertEqual(self.bundle.model.db, default) mock_update_default.assert_called_once_with(default) @patch.object(IPRoute, "try_update_default") @patch.object(IPRoute, "update_default") - def test__set_default__update_default_failed( + def test__set_default_routes__update_default_failed( self, mock_update_default, mock_try_update_default): """ - set_default: update default gateway failed + set_default_routes: update default gateway failed """ # arrange mock_update_default.side_effect = IOError - self.bundle.model.db["default"] = "eth1" - default = { - "interface": "eth0", - "gateway": "192.168.3.254" - } + self.bundle.model.db = ["eth1", "wwan0"] + default = ["wwan0", "eth0"] # act with self.assertRaises(IPRouteError): - self.bundle.set_default(default) + self.bundle.set_default_routes(default) # assert - self.assertEqual(self.bundle.model.db, {"default": "eth0"}) + self.assertEqual(self.bundle.model.db, default) mock_update_default.assert_called_once_with(default) mock_try_update_default.assert_called_once_with(self.bundle.model.db) @patch.object(IPRoute, "try_update_default") @patch.object(IPRoute, "update_default") - def test__set_default__update_default_and_recovery_failed( + def test__set_default_routes__update_default_and_recovery_failed( self, mock_update_default, mock_try_update_default): """ - set_default: update default gateway failed and recovery failed + set_default_routes: update default gateway failed and recovery failed """ # arrange mock_update_default.side_effect = IOError mock_try_update_default.side_effect = IOError - self.bundle.model.db["default"] = "eth1" - default = { - "interface": "eth0", - "gateway": "192.168.3.254" - } + self.bundle.model.db = ["eth1", "wwan0"] + default = ["wwan0", "eth0"] # act with self.assertRaises(IOError): - self.bundle.set_default(default) + self.bundle.set_default_routes(default) # assert - self.assertEqual(self.bundle.model.db, {"default": "eth0"}) + self.assertEqual(self.bundle.model.db, default) mock_update_default.assert_called_once_with(default) mock_try_update_default.assert_called_once_with(self.bundle.model.db) - def test__set_default__secondary(self): - """ - set_default: update secondary default gateway - """ - # arrange - self.bundle.model.db["default"] = "eth1" - default = { - "interface": "eth0", - "gateway": "192.168.3.254" - } - - # act - self.bundle.set_default(default, False) - - # assert - self.assertEqual(self.bundle.model.db, - {"default": "eth1", "secondary": "eth0"}) - - @patch("route.ip.route.delete") - def test__set_default__clear(self, mock_ip_route_del): - """ - set_default: clear default gateway - """ - # arrange - self.bundle.model.db["default"] = "eth1" - default = {} - - # act - self.bundle.set_default(default) - - # assert - self.assertEqual(self.bundle.model.db, {"default": ""}) - mock_ip_route_del.assert_called_once_with("default") - - @patch.object(IPRoute, "update_default") - def test__set_default__no_change(self, mock_update_default): - """ - set_default: default gateway doesn't change - """ - # arrange - self.bundle.model.db["default"] = "eth1" - default = {"gateway": "192.168.3.254"} - - # act - self.bundle.set_default(default) - - # assert - self.assertEqual(self.bundle.model.db, {"default": "eth1"}) - @patch.object(IPRoute, "update_default") def test__set_router_db__add(self, mock_update_default): """