-
Notifications
You must be signed in to change notification settings - Fork 108
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
Improves route control script #710
Improves route control script #710
Conversation
Breaks it into classes Adds type hints and docstrings Adds error handling Adds logging
Can one of the admins verify this patch? |
0f922e1
to
3ea8ee7
Compare
3ea8ee7
to
dd0aa92
Compare
Adds new test cases Fixes lint and static issues
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I feel like this code is pretty complex for what it's supposed to do. This PR increases the amount of lines of code by 50%. I understand that we have improved error handling but it also feels like we increased complexity
I understand why we want to go OOP, but the class selection and separation is "weird". I think we should spend a bit more time designing this. A class diagram could be useful.
I'm also yet unconvinced of the need for threads.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably should place tests in the project's test directory
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, we can move it no problem, but looking at the structure of the project and at some other test files being placed in many different locations, it becomes less clear to me which directory should hold these tests.
.github/workflows/py-workflow.yml
Outdated
- name: Install dependencies | ||
run: ./venv/bin/pip install -r conf/test_requirements.txt | ||
|
||
- name: Lint with flake8 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure about introducing mypy/flake8 here? I'd have a different PR for this as there can be discussions on theses points alone independent of the code implementation below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, this can and maybe should be part of different discussion :)
I removed flake8 and mypy for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(not related to this file)
In the PR description, please add context as to why we are making those changes in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
conf/test_route_control.py
Outdated
def tearDown(self): | ||
self.ipdb.release() | ||
|
||
def test_parse_valid_entry_and_dst_len_is_zero(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why drop the given/when/then test naming convention?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at tests in the projects, naming does not seem to follow a specific convention, tests seem to have descriptive names instead.
What do you think should use given/when/then? (Happy to do so)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went back to given/when/then ✅
conf/route_control.py
Outdated
parser = argparse.ArgumentParser( | ||
description='Basic IPv4 Routing Controller') | ||
parser.add_argument('-i', | ||
type=str, | ||
nargs='+', | ||
help='interface(s) to control') | ||
parser.add_argument('--ip', | ||
type=str, | ||
default='localhost', | ||
help='BESSD address') | ||
parser.add_argument('--port', type=str, default='10514', help='BESSD port') | ||
|
||
# for holding command-line arguments | ||
global args | ||
description="Basic IPv4 Routing Controller" | ||
) | ||
parser.add_argument( | ||
"-i", type=str, nargs="+", help="interface(s) to control" | ||
) | ||
parser.add_argument( | ||
"--ip", type=str, default="localhost", help="BESSD address" | ||
) | ||
parser.add_argument( | ||
"--port", type=str, default="10514", help="BESSD port" | ||
) | ||
|
||
args = parser.parse_args() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole block could have its own function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
conf/route_control.py
Outdated
self.ipdb = ipdb | ||
self.bess_controller = bess_controller | ||
self.route_parser = route_parser | ||
self.ping_missing = Thread(target=self._ping_missing_entries, daemon=True) # noqa: E501 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this really need to be multi-threaded? It feels overkilled for what we really need
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable name is somewhat confusing, it's really a thread that we are talking about here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It already is multi-threaded through ipdb
and the event callback. I think this design is better than the alternative (the main thread looping and pinging missing entries), as the thread's job is really clear. Also, if in the future we need to react to other events, or execute other checks, we can start small threads with clear focus.
conf/route_control.py
Outdated
|
||
DESTINATION_IP = "NDA_DST" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"NDA_DST" is not an IP address. These variables are confusing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improved the naming for these variables in general. ✅
conf/route_control.py
Outdated
|
||
def __init__(self, ipdb): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add typing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
conf/route_control.py
Outdated
|
||
|
||
def mac2hex(mac): | ||
return int(mac.replace(':', ''), 16) | ||
class RouteEntryParser: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more a function than an object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, moved.
class RouteController: | ||
def __init__( | ||
self, | ||
bess_controller: BessController, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does a RouteController have a bess_controller as an attribute?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RouteController uses the interface of BessController to add the routes in BESS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work, added some comments.
conf/route_control.py
Outdated
Args: | ||
update_module (str): The name of the module to delete. | ||
""" | ||
for i in range(MAX_RETRIES): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for i in range(MAX_RETRIES): | |
for _ in range(MAX_RETRIES): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
conf/route_control.py
Outdated
self.ipdb = ipdb | ||
self.bess_controller = bess_controller | ||
self.route_parser = route_parser | ||
self.ping_missing = Thread(target=self._ping_missing_entries, daemon=True) # noqa: E501 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It already is multi-threaded through ipdb
and the event callback. I think this design is better than the alternative (the main thread looping and pinging missing entries), as the thread's job is really clear. Also, if in the future we need to react to other events, or execute other checks, we can start small threads with clear focus.
conf/route_control.py
Outdated
unresolved_next_hop = self.unresolved_arp_queries_cache.get( | ||
attr_dict[DESTINATION_IP] | ||
) | ||
gateway_mac = attr_dict[LINK_LAYER_ADDRESS] | ||
if unresolved_next_hop: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unresolved_next_hop = self.unresolved_arp_queries_cache.get( | |
attr_dict[DESTINATION_IP] | |
) | |
gateway_mac = attr_dict[LINK_LAYER_ADDRESS] | |
if unresolved_next_hop: | |
route_entry = self.unresolved_arp_queries_cache.get( | |
attr_dict[DESTINATION_IP] | |
) | |
gateway_mac = attr_dict[LINK_LAYER_ADDRESS] | |
if route_entry: |
If we find the entry in the cache, it is not an unresolved next hop, since we just received the MAC address.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
conf/route_control.py
Outdated
Optional[str]: The MAC address of the target IP. | ||
""" | ||
neighbors = ipr.get_neighbours(dst=target_ip) | ||
for i in range(len(neighbors)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for i in range(len(neighbors)): | |
for neighbor in neighbors: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
77e6668
to
2d1c0d7
Compare
Renames constants Small changes in the design Extracts arg parsing
2d1c0d7
to
d8c96b7
Compare
@saltiyazan, you need to sign the CLA. So, the GitHub Actions can run |
conf/route_control.py
Outdated
DELETE_ROUTE_ACTION = "RTM_DELROUTE" | ||
NEW_ROUTE_ACTION = "RTM_NEWROUTE" | ||
INTERFACE = "RTA_OIF" | ||
DESTINATION_IP = "RTA_DST" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure of this variable name, it suggests it's an IP address.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same applies to a bunch of variables here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you that it is a bit confusing.
For now I added a "KEY_" prefix, that might make it clearer that these variables hold key literals and not actual values.
Happy to hear any suggestion you might have.
conf/test-requirements.txt
Outdated
@@ -0,0 +1,5 @@ | |||
pyroute2 | |||
scapy | |||
flake8 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need flake8/mypy to run the tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, removed
conf/route_control.py
Outdated
|
||
# for retrieving neighbor info | ||
from pyroute2 import IPDB, IPRoute | ||
from pybess.bess import * # type: ignore[import] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd remove those "# type: ignore[import]" if we're not implementing mypy here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This applies at a couple of places
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
conf/route_control.py
Outdated
ipr = IPRoute() | ||
ipdb = IPDB() | ||
bess_controller = BessController(ip_arg, port_arg) | ||
controller = RouteController( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
route_controller may be a better variable name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
conf/route_control.py
Outdated
""" | ||
self.bess = self.get_bess(ip=bess_ip, port=bess_port) | ||
|
||
def get_bess(self, ip: str, port: str) -> "BESS": # type: ignore[name-defined] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method should probably be private
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
conf/route_control.py
Outdated
"""Connects to the BESS daemon.""" | ||
bess = BESS() # type: ignore[name-defined] | ||
logger.info("Connecting to BESS daemon...") | ||
for _ in range(MAX_RETRIES): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MAX_RETRIES should probably be specific to the BessController class and not be global. I'd suggest either changing the name or placing it under the BessController class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved, same for SLEEP_S
conf/route_control.py
Outdated
gate_idx (int): Gate of the module used in the route. | ||
module_name (str): The name of the module. | ||
""" | ||
logger.info( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to have the logging both before and after the action was completed. I think we're good with the other logs (errors or success)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This applies at a couple of places
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some improvements. ✅
conf/route_control.py
Outdated
) | ||
) | ||
|
||
def del_module(self, module_name) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use "create", "link", "update", why not use "delete" (instead of "del")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
conf/route_control.py
Outdated
) | ||
) | ||
|
||
def del_module(self, module_name) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing typing for module name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
conf/route_control.py
Outdated
|
||
link_route_module(bess, gateway_mac, item) | ||
logger.info("Module %s destroyed", module_name) | ||
logger.info("BESS resume all") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This log doesn't match with what has been done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved
e917a36
to
e5616f0
Compare
Moves and renames constatns
e5616f0
to
1189218
Compare
Fixes mac_to_hex function Removes unnecessary int conversion
@saltiyazan, I am going to try to review your PR over the weekend. I am pretty busy until Thursday. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good but the unit tests could be improved. Our current implementation prevents future refactoring.
conf/test_route_control.py
Outdated
def test_given_valid_ip_when_validate_ipv4_then_returns_true(self): | ||
self.assertTrue(validate_ipv4("192.168.1.1")) | ||
self.assertFalse(validate_ipv4("192.168.300.1")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be 2 different tests, here we validate both the positive and negative outcomes
conf/test_route_control.py
Outdated
) | ||
self.assertIsNone(result) | ||
|
||
@patch.object(RouteController, "_handle_new_route_entry") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not patch private methods. With the state of the tests right now (we patch a _parse_route_entry_msg
, _parse_route_entry_msg
, _probe_addr
and more), we can't refactor the code and expect the unit tests to still pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I improved the tests.
Now we don't patch anything private.
Removes patches of private functions Uses MAX_GATES-1 as gate index for default routes
77b3f85
to
f83da1d
Compare
ok to test |
Awesome @gab-arrobo , should I run some tests manually that are not ran by the CI? could you please tell me which ones? reading through the testing section I see that "PTF tests for BESS-UPF" might be the one not ran by the CI. |
retest this please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @saltiyazan, thanks for your contribution and sorry for the time it took me to review it.
There is an issue with your modifications because for some reason the output of the pipeline for the N6 interface (in my case, ens801f1) is not getting properly built/assembled. Attached are two files showing the pipeline. The one named current-routectl.svg
shows the UPF pipeline using the current route-control.py
script, while the other figure (new-routectl.svg
) shows the UPF pipeline using your modified/improved route-control.py
script and as you can see in the new-routectl.svg
, the ens801f1Routes
, ens801f1DstMACCB....
and ens801f1Merge
are not getting connected and the route-control.py
script is "responsible" for that connection. Please take a look at the issue and properly address it. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
conf/route_control.py
Outdated
if route_entry.prefix_len == 0 and route_entry.dest_prefix == "0.0.0.0": | ||
gate_idx = self.MAX_GATES - 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue with the pipeline for the output of the N6 interface is due to this if
statement. This route 0.0.0.0/0
is/was already added to the routes
module and connected to the bad_route
module using routes
ogate 8191 in the ports.py. So, you cannot use it again the same ogate
and this is why the output of the N6 pipeline does not get properly connected/linked.
conf/route_control.py
Outdated
if route_entry.prefix_len == 0 and route_entry.dest_prefix == "0.0.0.0": | ||
gate_idx = self.MAX_GATES - 1 | ||
else: | ||
print("The IP address {} is valid ipv6 address. Ignore ".format(ipb)) | ||
return | ||
except: | ||
print("The IP address {} is invalid".format(item.neighbor_ip)) | ||
return | ||
# Probe ARP request by sending ping | ||
send_ping(item.neighbor_ip) | ||
|
||
# Probe ARP request | ||
##send_arp(neighbor_ip, src_mac, item.iface) | ||
|
||
|
||
def parse_new_route(msg): | ||
item = NeighborEntry() | ||
# Fetch prefix_len | ||
item.prefix_len = msg['dst_len'] | ||
# Default route | ||
if item.prefix_len == 0: | ||
item.iprange = '0.0.0.0' | ||
|
||
for att in msg['attrs']: | ||
if 'RTA_DST' in att: | ||
# Fetch IP range | ||
# ('RTA_DST', iprange) | ||
item.iprange = att[1] | ||
if 'RTA_GATEWAY' in att: | ||
# Fetch gateway MAC address | ||
# ('RTA_GATEWAY', neighbor_ip) | ||
item.neighbor_ip = att[1] | ||
_mac = fetch_mac(att[1]) | ||
if not _mac: | ||
gateway_mac = 0 | ||
else: | ||
gateway_mac = mac2hex(_mac) | ||
if 'RTA_OIF' in att: | ||
# Fetch interface name | ||
# ('RTA_OIF', iface) | ||
item.iface = ipdb.interfaces[int(att[1])].ifname | ||
|
||
if not item.iface in args.i or not item.iprange or not item.neighbor_ip: | ||
# Neighbor info is invalid | ||
del item | ||
return | ||
|
||
# if mac is 0, send ARP request | ||
if gateway_mac == 0: | ||
print('Adding entry {} in arp probe table. Neighbor: {}'.format(item.iface,item.neighbor_ip)) | ||
probe_addr(item, ipdb.interfaces[item.iface].address) | ||
|
||
else: # if gateway_mac is set | ||
print('Linking module {}Routes with {}Merge (Dest MAC: {})'.format( | ||
item.iface, item.iface, _mac)) | ||
|
||
link_route_module(bess, gateway_mac, item) | ||
|
||
|
||
def parse_new_neighbor(msg): | ||
for att in msg['attrs']: | ||
if 'NDA_DST' in att: | ||
# ('NDA_DST', neighbor_ip) | ||
neighbor_ip = att[1] | ||
if 'NDA_LLADDR' in att: | ||
# ('NDA_LLADDR', neighbor_mac) | ||
gateway_mac = att[1] | ||
|
||
item = arpcache.get(neighbor_ip) | ||
if item: | ||
print('Linking module {}Routes with {}Merge (Dest MAC: {})'.format( | ||
item.iface, item.iface, gateway_mac)) | ||
|
||
# Add route entry, and add item in the registered neighbor cache | ||
link_route_module(bess, mac2hex(gateway_mac), item) | ||
|
||
# Remove entry from unresolved arp cache | ||
del arpcache[neighbor_ip] | ||
|
||
|
||
def parse_del_route(msg): | ||
item = NeighborEntry() | ||
for att in msg['attrs']: | ||
if 'RTA_DST' in att: | ||
# Fetch IP range | ||
# ('RTA_DST', iprange) | ||
item.iprange = att[1] | ||
if 'RTA_GATEWAY' in att: | ||
# Fetch gateway MAC address | ||
# ('RTA_GATEWAY', neighbor_ip) | ||
item.neighbor_ip = att[1] | ||
if 'RTA_OIF' in att: | ||
# Fetch interface name | ||
# ('RTA_OIF', iface) | ||
item.iface = ipdb.interfaces[int(att[1])].ifname | ||
gate_idx = self._get_gate_idx(route_entry, route_module_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the route for 0.0.0.0/0
is already set in the ports.py
, a simple solution for the pipeline to be fully built/assembled would be to replace the following:
if route_entry.prefix_len == 0 and route_entry.dest_prefix == "0.0.0.0":
gate_idx = self.MAX_GATES - 1
else:
gate_idx = self._get_gate_idx(route_entry, route_module_name)
by this:
gate_idx = self._get_gate_idx(route_entry, route_module_name)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @gab-arrobo
Thanks for looking into it. I made the suggested change :) Let me know if it's all good now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good. I am planning to approve and merge it later tonight. Again, thanks for your contribution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested and works as expected.
Hi @saltiyazan, quick question. I just noticed the message below in the GHA. Is it expected?
|
Yes, it is expected, this is logged by
|
Ok, thanks for the clarification. |
@saltiyazan, any comments/thoughts about the deprecation of IPDB shown in the
|
@gab-arrobo I added it to the list of possible improvements our team could contribute to. |
def test_given_invalid_ip_when_validate_ipv4_then_returns_false(self): | ||
self.assertFalse(validate_ipv4("192.168.300.1")) | ||
|
||
def test_given_invalid_ip_when_validate_ipv4_then_returns_false(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @saltiyazan, I am working on replacing the IPDB for the NDB
and while doing so, I noticed that this test has the exact same name as the previous definition/function. Should it be IPv6 instead of IPv4?
- def test_given_invalid_ip_when_validate_ipv4_then_returns_false(self):
+ def test_given_invalid_ip_when_validate_ipv6_then_returns_false(self):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @gab-arrobo
It seems like this should have been one test case asserting on different values.
Improve the route_control.py script by adding logging, error handling, and threading.
This PR was motivated by the goal of improving logging and adding a thread that retries pinging endpoints that are not in the ARP table. (In case of packet loss for example when pinged for the first time). Other refactoring work has been done as well to improve the readability of the code and reduce complexity.