Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interface stringification #317

Merged
merged 7 commits into from Apr 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -456,28 +456,28 @@
" }</style><table id=\"T_pybfstyle\" class=\"tex2jax_ignore\"><thead> <tr> <th class=\"blank level0\" ></th> <th class=\"col_heading level0 col0\" >Interface</th> <th class=\"col_heading level0 col1\" >IPs</th> <th class=\"col_heading level0 col2\" >Remote_Interface</th> <th class=\"col_heading level0 col3\" >Remote_IPs</th> </tr></thead><tbody>\n",
" <tr>\n",
" <th id=\"T_pybfstylelevel0_row0\" class=\"row_heading level0 row0\" >36</th>\n",
" <td id=\"T_pybfstylerow0_col0\" class=\"data row0 col0\" >seattle:GigabitEthernet1/0</td>\n",
" <td id=\"T_pybfstylerow0_col0\" class=\"data row0 col0\" >seattle[GigabitEthernet1/0]</td>\n",
" <td id=\"T_pybfstylerow0_col1\" class=\"data row0 col1\" >2.12.22.1</td>\n",
" <td id=\"T_pybfstylerow0_col2\" class=\"data row0 col2\" >sanfrancisco:GigabitEthernet0/0</td>\n",
" <td id=\"T_pybfstylerow0_col2\" class=\"data row0 col2\" >sanfrancisco[GigabitEthernet0/0]</td>\n",
" <td id=\"T_pybfstylerow0_col3\" class=\"data row0 col3\" >2.12.22.2</td>\n",
" </tr>\n",
" <tr>\n",
" <th id=\"T_pybfstylelevel0_row1\" class=\"row_heading level0 row1\" >41</th>\n",
" <td id=\"T_pybfstylerow1_col0\" class=\"data row1 col0\" >seattle:GigabitEthernet2/0</td>\n",
" <td id=\"T_pybfstylerow1_col0\" class=\"data row1 col0\" >seattle[GigabitEthernet2/0]</td>\n",
" <td id=\"T_pybfstylerow1_col1\" class=\"data row1 col1\" >2.12.21.1</td>\n",
" <td id=\"T_pybfstylerow1_col2\" class=\"data row1 col2\" >philadelphia:GigabitEthernet1/0</td>\n",
" <td id=\"T_pybfstylerow1_col2\" class=\"data row1 col2\" >philadelphia[GigabitEthernet1/0]</td>\n",
" <td id=\"T_pybfstylerow1_col3\" class=\"data row1 col3\" >2.12.21.2</td>\n",
" </tr>\n",
" </tbody></table>"
],
"text/plain": [
" Interface IPs \\\n",
"36 seattle:GigabitEthernet1/0 ['2.12.22.1'] \n",
"41 seattle:GigabitEthernet2/0 ['2.12.21.1'] \n",
" Interface IPs \\\n",
"36 seattle[GigabitEthernet1/0] ['2.12.22.1'] \n",
"41 seattle[GigabitEthernet2/0] ['2.12.21.1'] \n",
"\n",
" Remote_Interface Remote_IPs \n",
"36 sanfrancisco:GigabitEthernet0/0 ['2.12.22.2'] \n",
"41 philadelphia:GigabitEthernet1/0 ['2.12.21.2'] "
" Remote_Interface Remote_IPs \n",
"36 sanfrancisco[GigabitEthernet0/0] ['2.12.22.2'] \n",
"41 philadelphia[GigabitEthernet1/0] ['2.12.21.2'] "
]
},
"metadata": {},
Expand Down Expand Up @@ -1411,7 +1411,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.0"
"version": "3.7.2"
}
},
"nbformat": 4,
Expand Down
30 changes: 15 additions & 15 deletions jupyter_notebooks/Getting started with Batfish.ipynb
Expand Up @@ -841,15 +841,15 @@
" <tbody>\n",
" <tr>\n",
" <th>22</th>\n",
" <td>as2border1:GigabitEthernet0/0</td>\n",
" <td>as2border1[GigabitEthernet0/0]</td>\n",
" <td>1e+09</td>\n",
" <td>PHYSICAL</td>\n",
" <td>10.12.11.2/24</td>\n",
" <td>default</td>\n",
" </tr>\n",
" <tr>\n",
" <th>26</th>\n",
" <td>as1border1:GigabitEthernet1/0</td>\n",
" <td>as1border1[GigabitEthernet1/0]</td>\n",
" <td>1e+09</td>\n",
" <td>PHYSICAL</td>\n",
" <td>10.12.11.1/24</td>\n",
Expand All @@ -860,9 +860,9 @@
"</div>"
],
"text/plain": [
" Interface Bandwidth Interface_Type Primary_Address \\\n",
"22 as2border1:GigabitEthernet0/0 1e+09 PHYSICAL 10.12.11.2/24 \n",
"26 as1border1:GigabitEthernet1/0 1e+09 PHYSICAL 10.12.11.1/24 \n",
" Interface Bandwidth Interface_Type Primary_Address \\\n",
"22 as2border1[GigabitEthernet0/0] 1e+09 PHYSICAL 10.12.11.2/24 \n",
"26 as1border1[GigabitEthernet1/0] 1e+09 PHYSICAL 10.12.11.1/24 \n",
"\n",
" VRF \n",
"22 default \n",
Expand Down Expand Up @@ -1009,30 +1009,30 @@
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>as1border1:GigabitEthernet0/0</td>\n",
" <td>as1border1[GigabitEthernet0/0]</td>\n",
" <td>['1.0.1.1']</td>\n",
" <td>as1core1:GigabitEthernet1/0</td>\n",
" <td>as1core1[GigabitEthernet1/0]</td>\n",
" <td>['1.0.1.2']</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>as1border1:GigabitEthernet1/0</td>\n",
" <td>as1border1[GigabitEthernet1/0]</td>\n",
" <td>['10.12.11.1']</td>\n",
" <td>as2border1:GigabitEthernet0/0</td>\n",
" <td>as2border1[GigabitEthernet0/0]</td>\n",
" <td>['10.12.11.2']</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Interface IPs \\\n",
"0 as1border1:GigabitEthernet0/0 ['1.0.1.1'] \n",
"1 as1border1:GigabitEthernet1/0 ['10.12.11.1'] \n",
" Interface IPs \\\n",
"0 as1border1[GigabitEthernet0/0] ['1.0.1.1'] \n",
"1 as1border1[GigabitEthernet1/0] ['10.12.11.1'] \n",
"\n",
" Remote_Interface Remote_IPs \n",
"0 as1core1:GigabitEthernet1/0 ['1.0.1.2'] \n",
"1 as2border1:GigabitEthernet0/0 ['10.12.11.2'] "
" Remote_Interface Remote_IPs \n",
"0 as1core1[GigabitEthernet1/0] ['1.0.1.2'] \n",
"1 as2border1[GigabitEthernet0/0] ['10.12.11.2'] "
]
},
"execution_count": 16,
Expand Down
26 changes: 13 additions & 13 deletions jupyter_notebooks/Validating Configuration Settings.ipynb
Expand Up @@ -742,46 +742,46 @@
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>as1border1:Ethernet0/0</td>\n",
" <td>as1border1[Ethernet0/0]</td>\n",
" <td>1500</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>as3border2:Ethernet0/0</td>\n",
" <td>as3border2[Ethernet0/0]</td>\n",
" <td>1500</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>as3border1:Ethernet0/0</td>\n",
" <td>as3border1[Ethernet0/0]</td>\n",
" <td>1500</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>as1border2:Ethernet0/0</td>\n",
" <td>as1border2[Ethernet0/0]</td>\n",
" <td>1500</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>as2border2:Ethernet0/0</td>\n",
" <td>as2border2[Ethernet0/0]</td>\n",
" <td>1500</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>as2border1:Ethernet0/0</td>\n",
" <td>as2border1[Ethernet0/0]</td>\n",
" <td>1500</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Interface MTU\n",
"0 as1border1:Ethernet0/0 1500\n",
"1 as3border2:Ethernet0/0 1500\n",
"2 as3border1:Ethernet0/0 1500\n",
"3 as1border2:Ethernet0/0 1500\n",
"4 as2border2:Ethernet0/0 1500\n",
"5 as2border1:Ethernet0/0 1500"
" Interface MTU\n",
"0 as1border1[Ethernet0/0] 1500\n",
"1 as3border2[Ethernet0/0] 1500\n",
"2 as3border1[Ethernet0/0] 1500\n",
"3 as1border2[Ethernet0/0] 1500\n",
"4 as2border2[Ethernet0/0] 1500\n",
"5 as2border1[Ethernet0/0] 1500"
]
},
"execution_count": 9,
Expand Down
8 changes: 4 additions & 4 deletions pybatfish/datamodel/primitives.py
Expand Up @@ -18,7 +18,7 @@
import attr
from pandas.core.indexes.frozen import FrozenList

from pybatfish.util import escape_html, get_html
from pybatfish.util import escape_html, escape_name, get_html

__all__ = ['Assertion',
'AssertionType',
Expand Down Expand Up @@ -203,12 +203,12 @@ def from_dict(cls, json_dict):

def __str__(self):
# type: () -> str
return "{}:{}".format(self.hostname, self.interface)
return "{}[{}]".format(escape_name(self.hostname),
escape_name(self.interface))

def _repr_html_(self):
# type: () -> str
return "{}:{}".format(escape_html(self.hostname),
escape_html(self.interface))
return "{}".format(escape_html(self.__str__()))


def _interface_converter(val):
Expand Down
18 changes: 18 additions & 0 deletions pybatfish/util.py
Expand Up @@ -38,10 +38,15 @@
# See issue https://bugs.python.org/issue34097
_MIN_ZIP_TIMESTAMP = 315561600.0

# Characters that must be escaped in name
# Should be in sync with SPECIAL_CHARS in CommonParser.java
_NAME_SPECIAL_CHARS_ = " \t,\\&()[]@" + "!#$%^;?<>={}"

__all__ = [
'BfJsonEncoder',
'conditional_str',
'escape_html',
'escape_name',
'get_html',
'get_uuid',
'validate_name',
Expand Down Expand Up @@ -192,6 +197,19 @@ def escape_html(s):
return escape(s)


def escape_name(s):
# type: (str) -> str
"""
Escapes the given name string with double quotes if needed.

A name should be quoted if it begins with '"', '/', or digit, or if it
contains a special character (_NAME_SPECIAL_CHARS_).
"""
return "\"{}\"".format(s) if s is not None and len(s) != 0 and (
s.startswith("\"") or s.startswith("/") or s[0].isdigit() or
any(s.find(c) > 0 for c in _NAME_SPECIAL_CHARS_)) else s


def get_html(element):
"""Attempts to call `_repr_html_()` to get HTML representation of object."""
try:
Expand Down
7 changes: 0 additions & 7 deletions tests/datamodel/test_datamodel_element.py
Expand Up @@ -38,10 +38,3 @@ def test_json_serialization():
# Load into dict from json to ignore key ordering
assert json.loads(BfJsonEncoder().encode(i)) == json.loads(
json.dumps(i.dict()))


def test_html_interface():
i = Interface(hostname='host', interface='special&')
assert i._repr_html_() == "host:special&amp;"
i = Interface(hostname='host', interface='normal:0/0.0')
assert i._repr_html_() == "host:normal:0/0.0"
44 changes: 44 additions & 0 deletions tests/datamodel/test_interface.py
@@ -0,0 +1,44 @@
# Copyright 2018 The Batfish Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import, print_function

import pytest

# test if an acl trace is deserialized properly
from pybatfish.datamodel.primitives import Interface


def test_str():
# no escaping
assert str(Interface(hostname="node", interface="iface")) == 'node[iface]'

# escape hostname
assert str(
Interface(hostname="0node", interface="iface")) == '"0node"[iface]'

# escape interface
assert str(
Interface(hostname="node", interface="/iface")) == 'node["/iface"]'


def test_html():
i = Interface(hostname='host', interface='special&')
assert i._repr_html_() == "host[&quot;special&amp;&quot;]"
i = Interface(hostname='host', interface='normal:0/0.0')
assert i._repr_html_() == "host[normal:0/0.0]"


if __name__ == "__main__":
pytest.main()
14 changes: 12 additions & 2 deletions tests/util/test_util.py
Expand Up @@ -21,8 +21,8 @@
from pybatfish.datamodel import AclTrace, Interface
from pybatfish.exception import QuestionValidationException
from pybatfish.util import (BfJsonEncoder, conditional_str, escape_html,
get_html, validate_name, validate_question_name,
zip_dir)
escape_name, get_html, validate_name,
validate_question_name, zip_dir)


def test_conditional_str():
Expand Down Expand Up @@ -103,6 +103,16 @@ def test_escape_html():
assert escape_html('a & b') == 'a &amp; b'


def test_escape_name():
assert escape_name('') == ''
assert escape_name('a') == 'a'
assert escape_name('abc') == 'abc'
assert escape_name('"a') == '""a"'
assert escape_name('/a') == '"/a"'
assert escape_name('0a') == '"0a"'
assert escape_name('a#') == '"a#"'


def test_get_html():
assert get_html('astring') == 'astring'
assert get_html(1.2) == '1.2'
Expand Down