Skip to content

Commit 2babaf4

Browse files
feat: Interfaces now support setting the logical router #610
1 parent 9048f70 commit 2babaf4

4 files changed

Lines changed: 225 additions & 33 deletions

File tree

panos/base.py

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,42 +1506,19 @@ def _convert_var(cls, value, vartype):
15061506
elif vartype == "bool":
15071507
return yesno(value)
15081508

1509-
def _set_reference(
1509+
def _get_all_objects_by_type(
15101510
self,
1511-
reference_name,
15121511
reference_type,
1513-
reference_var,
1514-
var_type,
1515-
exclusive,
15161512
refresh,
1517-
update,
15181513
running_config,
1519-
return_type,
15201514
name_only,
1521-
**kwargs
1515+
reference_var
15221516
):
1523-
"""Used by helper methods to set references between objects
1524-
1525-
For example, set_zone() would set the zone for an interface by creating a reference from
1526-
the zone to the interface. If the desired reference already exists then nothing happens.
1527-
1528-
This function has two modes: refresh=True and refresh=False. You
1529-
should only ever use refresh=False if:
1530-
1531-
1) all reference objects are in the current pan-os-python object tree
1532-
2) all reference objects are children attached to nearest_pandevice()
1533-
3) this is for firewall only, not a template / template stack
1534-
4) you're using firewall.vsys, not the device.Vsys object
1535-
1536-
If any of the above do not apply, you should be using refresh=True.
1537-
1517+
"""
1518+
Returns all of the objects of the given type, ensuring that `reference_var` is refreshed and available to be
1519+
set, and the parent pandevice
15381520
"""
15391521
parent = None
1540-
update_needed = False
1541-
1542-
if return_type not in ("bool", "object"):
1543-
raise ValueError("Unknown return_type specified: {0}".format(return_type))
1544-
15451522
if refresh:
15461523
"""
15471524
pan-os-python is too flexible: users can use simple vsys mode or a
@@ -1616,6 +1593,22 @@ def _set_reference(
16161593
parent = self.nearest_pandevice()
16171594
allobjects = parent.findall(reference_type)
16181595

1596+
return parent, allobjects
1597+
1598+
def _update_reference_in_objects(
1599+
self,
1600+
parent,
1601+
allobjects,
1602+
reference_name,
1603+
reference_type,
1604+
reference_var,
1605+
var_type,
1606+
exclusive,
1607+
update,
1608+
return_type,
1609+
**kwargs
1610+
):
1611+
update_needed = False
16191612
# Find any current references to self and remove them, unless it is the desired reference
16201613
if exclusive:
16211614
for obj in allobjects:
@@ -1678,6 +1671,61 @@ def _set_reference(
16781671
if return_type == "bool":
16791672
return update_needed
16801673

1674+
def _set_reference(
1675+
self,
1676+
reference_name,
1677+
reference_type,
1678+
reference_var,
1679+
var_type,
1680+
exclusive,
1681+
refresh,
1682+
update,
1683+
running_config,
1684+
return_type,
1685+
name_only,
1686+
**kwargs
1687+
):
1688+
"""Used by helper methods to set references between objects
1689+
1690+
For example, set_zone() would set the zone for an interface by creating a reference from
1691+
the zone to the interface. If the desired reference already exists then nothing happens.
1692+
1693+
This function has two modes: refresh=True and refresh=False. You
1694+
should only ever use refresh=False if:
1695+
1696+
1) all reference objects are in the current pan-os-python object tree
1697+
2) all reference objects are children attached to nearest_pandevice()
1698+
3) this is for firewall only, not a template / template stack
1699+
4) you're using firewall.vsys, not the device.Vsys object
1700+
1701+
If any of the above do not apply, you should be using refresh=True.
1702+
1703+
"""
1704+
if return_type not in ("bool", "object"):
1705+
raise ValueError("Unknown return_type specified: {0}".format(return_type))
1706+
1707+
# Get all the objects and the parent
1708+
parent, allobjects = self._get_all_objects_by_type(
1709+
reference_type,
1710+
refresh,
1711+
running_config,
1712+
name_only,
1713+
reference_var
1714+
)
1715+
1716+
return self._update_reference_in_objects(
1717+
parent,
1718+
allobjects,
1719+
reference_name,
1720+
reference_type,
1721+
reference_var,
1722+
var_type,
1723+
exclusive,
1724+
update,
1725+
return_type,
1726+
**kwargs
1727+
)
1728+
16811729
def xml_merge(self, root, elements):
16821730
"""Merges other elements into the root element.
16831731

panos/network.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import logging
2121
import re
2222
import xml.etree.ElementTree as ET
23+
from keyword import kwlist
2324

2425
import panos
2526
import panos.errors as err
@@ -522,6 +523,74 @@ def set_vlan(
522523
False,
523524
)
524525

526+
def set_logical_router(
527+
self,
528+
lr_name,
529+
refresh=False,
530+
update=False,
531+
running_config=False,
532+
return_type="object",
533+
vrf_name="default",
534+
**kwargs
535+
):
536+
"""adds the given interface to the VRF by name.
537+
538+
This is more complicated than `set_virtual_router` as the logical routers have child VRF child elements, which
539+
is where the interfaces are configured.
540+
541+
This will use the VRF name 'default' by default.
542+
543+
Args:
544+
logical_router_name (str): The name of the LogicalRouter or
545+
a :class:`panos.network.LogicalRouter` instance
546+
refresh (bool): Refresh the relevant current state of the device
547+
before taking action (Default: False)
548+
update (bool): Apply the changes to the device (Default: False)
549+
running_config: If refresh is True, refresh from the running
550+
configuration (Default: False)
551+
return_type (str): Specify what this function returns, can be
552+
either 'object' (the default) or 'bool'. If this is 'object',
553+
then the return value is the LogicalRouter in question. If
554+
this is 'bool', then the return value is a boolean that tells
555+
you about if the live device needs updates (update=False) or
556+
was updated (update=True).
557+
"""
558+
559+
# First we get all the logical routers
560+
parent, all_logical_routers = self._get_all_objects_by_type(
561+
LogicalRouter,
562+
refresh,
563+
running_config,
564+
name_only=False,
565+
reference_var="interface"
566+
)
567+
lr: LogicalRouter | None
568+
569+
lr = next((lr for lr in all_logical_routers if lr.uid == lr_name), None)
570+
if not lr:
571+
# If the LR isn't found, create it instead
572+
lr = LogicalRouter(name=lr_name)
573+
parent.add(lr)
574+
vrf = Vrf(name=vrf_name)
575+
lr.vrf = [vrf]
576+
lr.create()
577+
578+
# Pass all the vrfs to the update method
579+
return self._update_reference_in_objects(
580+
lr,
581+
lr.vrf,
582+
reference_name=vrf_name,
583+
reference_var="interface",
584+
reference_type=Vrf,
585+
var_type="list",
586+
return_type=return_type,
587+
update=update,
588+
exclusive=True,
589+
**kwargs
590+
)
591+
592+
593+
525594
def get_counters(self):
526595
"""Pull the counters for an interface
527596

tests/live/test_network.py

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import random
2+
import traceback
3+
from inspect import trace
24

35
from panos import device, network
6+
from panos.network import Interface
47
from tests.live import testlib
58

69

@@ -586,6 +589,44 @@ def cleanup_dependencies(self, fw, state):
586589
except Exception:
587590
pass
588591

592+
class TestVirtualRouterInterfaces(testlib.FwFlow):
593+
def create_dependencies(self, fw, state):
594+
# Disable ARE on the device so we're just using VR setup
595+
advanced_routing_engine = device.AdvancedRoutingEngine(enable=False)
596+
fw.add(advanced_routing_engine)
597+
advanced_routing_engine.create()
598+
599+
state.eth_obj = None
600+
state.eth = testlib.get_available_interfaces(fw, max_interfaces=7)[0]
601+
602+
state.eth_obj = network.EthernetInterface(
603+
state.eth, "layer3", testlib.random_ip("/24")
604+
)
605+
fw.add(state.eth_obj)
606+
state.eth_obj.create()
607+
608+
state.obj = network.VirtualRouter(testlib.random_name())
609+
fw.add(state.obj)
610+
611+
def setup_state_obj(self, fw, state):
612+
pass
613+
614+
def test_10_set_vr(self, fw, state_map):
615+
state = self.sanity(fw, state_map)
616+
state.eth_obj.set_virtual_router(state.obj.name)
617+
618+
def cleanup_dependencies(self, fw, state):
619+
return
620+
try:
621+
state.vr.delete()
622+
except Exception:
623+
pass
624+
625+
try:
626+
state.eth_obj.delete()
627+
except Exception:
628+
pass
629+
589630

590631
class TestStaticRouteV6(testlib.FwFlow):
591632
def create_dependencies(self, fw, state):
@@ -1659,13 +1700,21 @@ def create_dependencies(self, fw, state):
16591700
state.advanced_routing_engine_obj.create()
16601701

16611702
state.eth_obj = None
1662-
state.eth = testlib.get_available_interfaces(fw)[0]
1703+
available_interfaces = testlib.get_available_interfaces(fw, 2, 7)
1704+
state.eth = available_interfaces[0]
1705+
state.eth_2 = available_interfaces[1]
16631706

16641707
state.eth_obj = network.EthernetInterface(
16651708
state.eth, "layer3", testlib.random_ip("/24")
16661709
)
1710+
state.eth_obj_2 = network.EthernetInterface(
1711+
state.eth_2, "layer3", testlib.random_ip("/24")
1712+
)
16671713
fw.add(state.eth_obj)
1714+
fw.add(state.eth_obj_2)
16681715
state.eth_obj.create()
1716+
state.eth_obj_2.create()
1717+
16691718

16701719
def setup_state_obj(self, fw, state):
16711720
vrf = network.Vrf(
@@ -1690,18 +1739,41 @@ def setup_state_obj(self, fw, state):
16901739
)
16911740
lr = network.LogicalRouter(testlib.random_name())
16921741
lr.add(vrf)
1742+
1743+
lr_2 = network.LogicalRouter(testlib.random_name())
1744+
vrf2 = network.Vrf(
1745+
"default"
1746+
)
1747+
lr_2.add(vrf2)
1748+
16931749
state.obj = lr
1750+
state.obj_2 = lr_2
16941751
fw.add(state.obj)
1752+
fw.add(state.obj_2)
1753+
state.obj.create()
1754+
state.obj_2.create()
1755+
1756+
def test_10_set_lr_for_interface(self, fw, state_map):
1757+
"""Test setting the LR for an interface instead of the other way around"""
1758+
state = self.sanity(fw, state_map)
1759+
eth: Interface = state.eth_obj_2
1760+
eth.set_logical_router(state.obj_2, update=True)
16951761

16961762
def update_state_obj(self, fw, state):
16971763
state.obj.ad_static = random.randint(10, 240)
16981764
state.obj.ad_rip = random.randint(10, 240)
16991765

17001766
def cleanup_dependencies(self, fw, state):
17011767
try:
1768+
fw.add(state.obj)
1769+
fw.add(state.obj_2)
1770+
state.obj.delete()
1771+
state.obj_2.delete()
17021772
state.eth_obj.delete()
1703-
state.advanced_routing_engine_obj.delete()
1704-
except Exception:
1773+
state.eth_obj_2.delete()
1774+
except Exception as e:
1775+
print(traceback.format_exc())
1776+
print(e)
17051777
pass
17061778

17071779

tests/live/testlib.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import random
2+
import traceback
23

34
import pytest
45

@@ -47,11 +48,11 @@ def random_mac():
4748
return ":".join("{0:02x}".format(random.randint(0, 255)) for x in range(6))
4849

4950

50-
def get_available_interfaces(con, num=1):
51+
def get_available_interfaces(con, num=1, max_interfaces=10):
5152
ifaces = network.EthernetInterface.refreshall(con, add=False)
5253
ifaces = set(x.name for x in ifaces)
5354

54-
all_interfaces = set("ethernet1/{0}".format(x) for x in range(1, 10))
55+
all_interfaces = set("ethernet1/{0}".format(x) for x in range(1, max_interfaces))
5556
available = all_interfaces.difference(ifaces)
5657

5758
ans = []
@@ -73,6 +74,8 @@ def test_01_setup_dependencies(self, fw, state_map):
7374
except Exception as e:
7475
print("SETUP ERROR: {0}".format(e))
7576
state.err = True
77+
print(traceback.format_exc())
78+
7679
pytest.skip("Setup failed")
7780

7881
def create_dependencies(self, fw, state):

0 commit comments

Comments
 (0)