Skip to content

Commit 3262fa5

Browse files
committed
feat: ipsec_tunnel_status check Proxy ID support
`ipsec_tunnel_status` check to accept a list of `proxy_ids` to check tunnel status against the provided list, checks for all configured Proxy IDs if not provided. Returns success if any Proxy ID is in active state for the ipsec tunnel or `require_all_active` flag can be set to require all Proxy IDs to be in active state.
1 parent 33ee9f1 commit 3262fa5

File tree

3 files changed

+213
-3
lines changed

3 files changed

+213
-3
lines changed

docs/panos-upgrade-assurance/api/check_firewall.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,9 @@ __Returns__
330330

331331
```python
332332
def check_ipsec_tunnel_status(
333-
tunnel_name: Optional[str] = None) -> CheckResult
333+
tunnel_name: Optional[str] = None,
334+
proxy_ids: Optional[List[str]] = None,
335+
require_all_active: Optional[bool] = False) -> CheckResult
334336
```
335337

336338
Check if a given IPSec tunnel is in active state.
@@ -339,6 +341,9 @@ __Parameters__
339341

340342

341343
- __tunnel_name__ (`str, optional`): (defaults to `None`) Name of the searched IPSec tunnel.
344+
- __proxy_ids__ (`list(str), optional`): (defaults to `None`) ProxyID names to check. All ProxyIDs are checked if None provided.
345+
- __require_all_active__ (`bool, optional`): (defaults to `False`) If set, all ProxyIDs should be in `active` state. States are
346+
checked only within `proxy_ids` if provided.
342347

343348
__Returns__
344349

panos_upgrade_assurance/check_firewall.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -593,12 +593,17 @@ def check_arp_entry(self, ip: Optional[str] = None, interface: Optional[str] = N
593593
result.reason = "Entry not found in ARP table."
594594
return result
595595

596-
def check_ipsec_tunnel_status(self, tunnel_name: Optional[str] = None) -> CheckResult:
596+
def check_ipsec_tunnel_status(
597+
self, tunnel_name: Optional[str] = None, proxy_ids: Optional[List[str]] = None, require_all_active: Optional[bool] = False
598+
) -> CheckResult:
597599
"""Check if a given IPSec tunnel is in active state.
598600
599601
# Parameters
600602
601603
tunnel_name (str, optional): (defaults to `None`) Name of the searched IPSec tunnel.
604+
proxy_ids (list(str), optional): (defaults to `None`) ProxyID names to check. All ProxyIDs are checked if None provided.
605+
require_all_active (bool, optional): (defaults to `False`) If set, all ProxyIDs should be in `active` state. States are
606+
checked only within `proxy_ids` if provided.
602607
603608
# Returns
604609
@@ -630,6 +635,8 @@ def check_ipsec_tunnel_status(self, tunnel_name: Optional[str] = None) -> CheckR
630635
result.status = CheckStatus.ERROR
631636
return result
632637

638+
ipsec_proxyids = [] # IPSec ProxyIDs that exist
639+
633640
for name in tunnels["IPSec"]:
634641
data = tunnels["IPSec"][name]
635642
if name == tunnel_name:
@@ -638,8 +645,41 @@ def check_ipsec_tunnel_status(self, tunnel_name: Optional[str] = None) -> CheckR
638645
else:
639646
result.reason = f"Tunnel {tunnel_name} in state: {data['state']}."
640647
return result
648+
elif name.startswith(f"{tunnel_name}:"):
649+
ipsec_proxyids.append(name.split(":")[-1])
650+
else:
651+
if not ipsec_proxyids: # ipsec tunnel not found with or without proxyids
652+
result.reason = f"Tunnel {tunnel_name} not found."
653+
return result
641654

642-
result.reason = f"Tunnel {tunnel_name} not found."
655+
proxyids_to_check = [] # IPSec ProxyIDs to check
656+
ipsec_proxyids_active = 0 # number of active ProxyIDs within proxyids_to_check
657+
658+
if proxy_ids:
659+
if set(proxy_ids).issubset(ipsec_proxyids):
660+
proxyids_to_check = proxy_ids
661+
else:
662+
result.reason = f"Tunnel {tunnel_name} has missing ProxyIDs in {proxy_ids}."
663+
return result
664+
else:
665+
proxyids_to_check = ipsec_proxyids
666+
667+
for proxy_id in proxyids_to_check:
668+
data = tunnels["IPSec"][f"{tunnel_name}:{proxy_id}"]
669+
if data["state"] == "active":
670+
ipsec_proxyids_active += 1
671+
elif require_all_active: # state not active but we require all active
672+
result.reason = f"Tunnel:ProxyID {tunnel_name}:{proxy_id} in state: {data['state']}."
673+
return result
674+
675+
if require_all_active:
676+
if proxyids_to_check and (len(proxyids_to_check) == ipsec_proxyids_active):
677+
result.status = CheckStatus.SUCCESS
678+
else:
679+
if ipsec_proxyids_active >= 1:
680+
result.status = CheckStatus.SUCCESS
681+
elif ipsec_proxyids_active == 0:
682+
result.reason = f"No active state for tunnel {tunnel_name} in ProxyIDs {proxyids_to_check}."
643683

644684
return result
645685

tests/test_check_firewall.py

+165
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,171 @@ def test_ipsec_tunnel_status_not_found(self, check_firewall_mock):
513513
reason="Tunnel NotMyTunnel not found."
514514
)
515515

516+
def test_ipsec_tunnel_status_proxyids_not_found(self, check_firewall_mock):
517+
"""tunnel with proxyids - proxyids given but not found"""
518+
check_firewall_mock._node.get_tunnels.return_value = {
519+
"IPSec": {
520+
"east1-vpn:ProxyID1": {"state": "active"},
521+
"east1-vpn:ProxyID2": {"state": "active"},
522+
"central1-vpn:ProxyID1": {"state": "init"},
523+
}
524+
}
525+
assert check_firewall_mock.check_ipsec_tunnel_status(
526+
tunnel_name="east1-vpn", proxy_ids=["ProxyID1", "ProxyID3"]
527+
) == CheckResult(reason="Tunnel east1-vpn has missing ProxyIDs in ['ProxyID1', 'ProxyID3'].")
528+
529+
@pytest.mark.parametrize(
530+
"require_all_active, expected_status",
531+
[
532+
(True, CheckStatus.SUCCESS),
533+
(False, CheckStatus.SUCCESS),
534+
],
535+
)
536+
def test_ipsec_tunnel_status_proxyids_all_active(self, require_all_active, expected_status, check_firewall_mock):
537+
"""tunnel with proxyids - proxyids given and all active
538+
Should return success whether require_all_active is True or False.
539+
"""
540+
check_firewall_mock._node.get_tunnels.return_value = {
541+
"IPSec": {
542+
"east1-vpn:ProxyID1": {"state": "active"},
543+
"east1-vpn:ProxyID2": {"state": "active"},
544+
"east1-vpn:ProxyID3": {"state": "active"},
545+
"central1-vpn:ProxyID1": {"state": "init"},
546+
}
547+
}
548+
assert check_firewall_mock.check_ipsec_tunnel_status(
549+
tunnel_name="east1-vpn", proxy_ids=["ProxyID1", "ProxyID2", "ProxyID3"], require_all_active=require_all_active
550+
) == CheckResult(expected_status)
551+
552+
@pytest.mark.parametrize(
553+
"require_all_active, expected_status, reason",
554+
[
555+
(True, CheckStatus.FAIL, "Tunnel:ProxyID east1-vpn:ProxyID3 in state: init."),
556+
(False, CheckStatus.SUCCESS, ""),
557+
],
558+
)
559+
def test_ipsec_tunnel_status_proxyids_some_active(self, require_all_active, expected_status, reason, check_firewall_mock):
560+
"""tunnel with proxyids - proxyids given and some active
561+
Should return fail by default. Success if require_all_active is False.
562+
"""
563+
check_firewall_mock._node.get_tunnels.return_value = {
564+
"IPSec": {
565+
"east1-vpn:ProxyID1": {"state": "active"},
566+
"east1-vpn:ProxyID2": {"state": "active"},
567+
"east1-vpn:ProxyID3": {"state": "init"},
568+
"central1-vpn:ProxyID1": {"state": "init"},
569+
}
570+
}
571+
assert check_firewall_mock.check_ipsec_tunnel_status(
572+
tunnel_name="east1-vpn", proxy_ids=["ProxyID1", "ProxyID2", "ProxyID3"], require_all_active=require_all_active
573+
) == CheckResult(expected_status, reason=reason)
574+
575+
@pytest.mark.parametrize(
576+
"require_all_active, expected_status, reason",
577+
[
578+
(True, CheckStatus.FAIL, "Tunnel:ProxyID east1-vpn:ProxyID1 in state: init."),
579+
(False, CheckStatus.FAIL, "No active state for tunnel east1-vpn in ProxyIDs ['ProxyID1', 'ProxyID2', 'ProxyID3']."),
580+
],
581+
)
582+
def test_ipsec_tunnel_status_proxyids_none_active(self, require_all_active, expected_status, reason, check_firewall_mock):
583+
"""tunnel with proxyids - proxyids given and all not active
584+
Should return fail whether require_all_active is True or False.
585+
"""
586+
check_firewall_mock._node.get_tunnels.return_value = {
587+
"IPSec": {
588+
"east1-vpn:ProxyID1": {"state": "init"},
589+
"east1-vpn:ProxyID2": {"state": "init"},
590+
"east1-vpn:ProxyID3": {"state": "init"},
591+
"central1-vpn:ProxyID1": {"state": "active"},
592+
}
593+
}
594+
assert check_firewall_mock.check_ipsec_tunnel_status(
595+
tunnel_name="east1-vpn", proxy_ids=["ProxyID1", "ProxyID2", "ProxyID3"], require_all_active=require_all_active
596+
) == CheckResult(expected_status, reason=reason)
597+
598+
@pytest.mark.parametrize(
599+
"require_all_active, expected_status",
600+
[
601+
(True, CheckStatus.SUCCESS),
602+
(False, CheckStatus.SUCCESS),
603+
],
604+
)
605+
def test_ipsec_tunnel_status_none_proxyids_all_active(self, require_all_active, expected_status, check_firewall_mock):
606+
"""tunnel with proxyids - proxyids not given and all active"""
607+
check_firewall_mock._node.get_tunnels.return_value = {
608+
"IPSec": {
609+
"east1-vpn:ProxyID1": {"state": "active"},
610+
"east1-vpn:ProxyID2": {"state": "active"},
611+
"east1-vpn:ProxyID3": {"state": "active"},
612+
"central1-vpn:ProxyID1": {"state": "init"},
613+
}
614+
}
615+
assert check_firewall_mock.check_ipsec_tunnel_status(
616+
tunnel_name="east1-vpn", require_all_active=require_all_active
617+
) == CheckResult(expected_status)
618+
619+
assert check_firewall_mock.check_ipsec_tunnel_status(
620+
tunnel_name="east1-vpn", proxy_ids=[], require_all_active=require_all_active
621+
) == CheckResult(expected_status)
622+
623+
@pytest.mark.parametrize(
624+
"require_all_active, expected_status, reason",
625+
[
626+
(True, CheckStatus.FAIL, "Tunnel:ProxyID east1-vpn:ProxyID2 in state: init."),
627+
(False, CheckStatus.SUCCESS, ""),
628+
],
629+
)
630+
def test_ipsec_tunnel_status_none_proxyids_some_active(
631+
self, require_all_active, expected_status, reason, check_firewall_mock
632+
):
633+
"""tunnel with proxyids - proxyids not given and some active
634+
Should return fail by default. Success if require_all_active is False.
635+
"""
636+
check_firewall_mock._node.get_tunnels.return_value = {
637+
"IPSec": {
638+
"east1-vpn:ProxyID1": {"state": "active"},
639+
"east1-vpn:ProxyID2": {"state": "init"},
640+
"east1-vpn:ProxyID3": {"state": "active"},
641+
"central1-vpn:ProxyID1": {"state": "init"},
642+
}
643+
}
644+
assert check_firewall_mock.check_ipsec_tunnel_status(
645+
tunnel_name="east1-vpn", require_all_active=require_all_active
646+
) == CheckResult(expected_status, reason=reason)
647+
648+
assert check_firewall_mock.check_ipsec_tunnel_status(
649+
tunnel_name="east1-vpn", proxy_ids=[], require_all_active=require_all_active
650+
) == CheckResult(expected_status, reason=reason)
651+
652+
@pytest.mark.parametrize(
653+
"require_all_active, expected_status, reason",
654+
[
655+
(True, CheckStatus.FAIL, "Tunnel:ProxyID east1-vpn:ProxyID1 in state: init."),
656+
(False, CheckStatus.FAIL, "No active state for tunnel east1-vpn in ProxyIDs ['ProxyID1', 'ProxyID2', 'ProxyID3']."),
657+
],
658+
)
659+
def test_ipsec_tunnel_status_none_proxyids_none_active(
660+
self, require_all_active, expected_status, reason, check_firewall_mock
661+
):
662+
"""tunnel with proxyids - proxyids not given and not active
663+
Should return fail whether require_all_active is True or False.
664+
"""
665+
check_firewall_mock._node.get_tunnels.return_value = {
666+
"IPSec": {
667+
"east1-vpn:ProxyID1": {"state": "init"},
668+
"east1-vpn:ProxyID2": {"state": "init"},
669+
"east1-vpn:ProxyID3": {"state": "init"},
670+
"central1-vpn:ProxyID1": {"state": "active"},
671+
}
672+
}
673+
assert check_firewall_mock.check_ipsec_tunnel_status(
674+
tunnel_name="east1-vpn", require_all_active=require_all_active
675+
) == CheckResult(expected_status, reason=reason)
676+
677+
assert check_firewall_mock.check_ipsec_tunnel_status(
678+
tunnel_name="east1-vpn", proxy_ids=[], require_all_active=require_all_active
679+
) == CheckResult(expected_status, reason=reason)
680+
516681
def test_check_free_disk_space_ok(self, check_firewall_mock):
517682
check_firewall_mock._node.get_disk_utilization.return_value = {"/opt/panrepo": 50000}
518683

0 commit comments

Comments
 (0)