diff options
-rw-r--r-- | op-mode-definitions/show-interfaces.xml.in | 6 | ||||
-rw-r--r-- | op-mode-definitions/show-vrf.xml.in | 14 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 10 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_service_https.py | 102 | ||||
-rwxr-xr-x | src/op_mode/interfaces.py | 56 |
5 files changed, 188 insertions, 0 deletions
diff --git a/op-mode-definitions/show-interfaces.xml.in b/op-mode-definitions/show-interfaces.xml.in index dc61a6f5c..b58e0efea 100644 --- a/op-mode-definitions/show-interfaces.xml.in +++ b/op-mode-definitions/show-interfaces.xml.in @@ -20,6 +20,12 @@ </properties> <command>${vyos_op_scripts_dir}/interfaces.py show</command> </leafNode> + <leafNode name="summary"> + <properties> + <help>Show summary information of all interfaces</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary_extended</command> + </leafNode> </children> </node> </children> diff --git a/op-mode-definitions/show-vrf.xml.in b/op-mode-definitions/show-vrf.xml.in index 9728eb1fa..c18649844 100644 --- a/op-mode-definitions/show-vrf.xml.in +++ b/op-mode-definitions/show-vrf.xml.in @@ -7,6 +7,14 @@ <help>Show VRF (Virtual Routing and Forwarding) information</help> </properties> <command>${vyos_op_scripts_dir}/vrf.py show</command> + <children> + <leafNode name="vni"> + <properties> + <help>Show information on VRF/VXLAN VNI mapping</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> </node> <tagNode name="vrf"> <properties> @@ -23,6 +31,12 @@ </properties> <command>ip vrf pids "$3"</command> </leafNode> + <leafNode name="vni"> + <properties> + <help>Show VXLAN VNI association</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> </children> </tagNode> </children> diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 41ce352ad..b8f947d65 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -569,6 +569,16 @@ class Interface(Control): self.set_interface('netns', netns) + def get_vrf(self): + """ + Get VRF from interface + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_vrf() + """ + return self.get_interface('vrf') + def set_vrf(self, vrf): """ Add/Remove interface from given VRF instance. diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 1ae5c104c..a18e7dfac 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -15,6 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import unittest +import json from requests import request from urllib3.exceptions import InsecureRequestWarning @@ -138,6 +139,13 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): # Must get HTTP code 401 on missing key (Unauthorized) self.assertEqual(r.status_code, 401) + # Check path config + payload = {'data': '{"op": "showConfig", "path": ["system", "login"]}', 'key': f'{key}'} + r = request('POST', url, verify=False, headers=headers, data=payload) + response = r.json() + vyos_user_exists = 'vyos' in response.get('data', {}).get('user', {}) + self.assertTrue(vyos_user_exists, "The 'vyos' user does not exist in the response.") + # GraphQL auth test: a missing key will return status code 400, as # 'key' is a non-nullable field in the schema; an incorrect key is # caught by the resolver, and returns success 'False', so one must @@ -240,5 +248,99 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): success = r.json()['data']['ShowVersion']['success'] self.assertTrue(success) + @ignore_warning(InsecureRequestWarning) + def test_api_show(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/show' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "show", "path": ["system", "image"]}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_generate(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/generate' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "generate", "path": ["macsec", "mka", "cak", "gcm-aes-256"]}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_configure(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/configure' + headers = {} + conf_interface = 'dum0' + conf_address = '192.0.2.44/32' + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload_path = [ + "interfaces", + "dummy", + f"{conf_interface}", + "address", + f"{conf_address}", + ] + + payload = {'data': json.dumps({"op": "set", "path": payload_path}), 'key': key} + + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_config_file(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/config-file' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "save"}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_reset(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/reset' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "reset", "path": ["ip", "arp", "table"]}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py index 782e178c6..c626535b5 100755 --- a/src/op_mode/interfaces.py +++ b/src/op_mode/interfaces.py @@ -243,6 +243,9 @@ def _get_summary_data(ifname: typing.Optional[str], res_intf['admin_state'] = interface.get_admin_state() res_intf['addr'] = [_ for _ in interface.get_addr() if not _.startswith('fe80::')] res_intf['description'] = interface.get_alias() + res_intf['mtu'] = interface.get_mtu() + res_intf['mac'] = interface.get_mac() + res_intf['vrf'] = interface.get_vrf() ret.append(res_intf) @@ -373,6 +376,51 @@ def _format_show_summary(data): return 0 @catch_broken_pipe +def _format_show_summary_extended(data): + headers = ["Interface", "IP Address", "MAC", "VRF", "MTU", "S/L", "Description"] + table_data = [] + + print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down') + + for intf in data: + if 'unhandled' in intf: + continue + + ifname = intf['ifname'] + oper_state = 'u' if intf['oper_state'] in ('up', 'unknown') else 'D' + admin_state = 'u' if intf['admin_state'] in ('up', 'unknown') else 'A' + addrs = intf['addr'] or ['-'] + description = '\n'.join(_split_text(intf['description'], 0)) + mac = intf['mac'] if intf['mac'] else 'n/a' + mtu = intf['mtu'] if intf['mtu'] else 'n/a' + vrf = intf['vrf'] if intf['vrf'] else 'default' + + ip_addresses = '\n'.join(ip for ip in addrs) + + # Create a row for the table + row = [ + ifname, + ip_addresses, + mac, + vrf, + mtu, + f"{admin_state}/{oper_state}", + description, + ] + + # Append the row to the table data + table_data.append(row) + + for intf in data: + if 'unhandled' in intf: + string = {'C': 'u/D', 'D': 'A/D'}[intf['state']] + table_data.append([intf['ifname'], '', '', '', '', string, '']) + + print(tabulate(table_data, headers)) + + return 0 + +@catch_broken_pipe def _format_show_counters(data: list): data_entries = [] for entry in data: @@ -408,6 +456,14 @@ def show_summary(raw: bool, intf_name: typing.Optional[str], return data return _format_show_summary(data) +def show_summary_extended(raw: bool, intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + data = _get_summary_data(intf_name, intf_type, vif, vrrp) + if raw: + return data + return _format_show_summary_extended(data) + def show_counters(raw: bool, intf_name: typing.Optional[str], intf_type: typing.Optional[str], vif: bool, vrrp: bool): |