Skip to content

Commit 59f6126

Browse files
authored
Containment based queries optimized (#537)
* Optimized implementation of EquivalenceQuery. Signed-off-by: Tanya <[email protected]> * Added VacuityQuery and RedundancyQuery optimized implementation. Keeping optimized properties separated per rule (instead of the union of all policy rules) Fixed handling HostEPs in optimized implementation. Signed-off-by: Tanya <[email protected]> * Added VacuityQuery and RedundancyQuery optimized implementation. Keeping optimized properties separated per rule (instead of the union of all policy rules) Fixed handling HostEPs in optimized implementation. Signed-off-by: Tanya <[email protected]> * Ignoring 'complex function' lint error. Returning 'passed' code for skipped queries. Signed-off-by: Tanya <[email protected]> * Added VacuityQuery and RedundancyQuery optimized implementation. Keeping optimized properties separated per rule (instead of the union of all policy rules) Fixed handling HostEPs in optimized implementation. Signed-off-by: Tanya <[email protected]> * Removed redundant method. Signed-off-by: Tanya <[email protected]> * Added VacuityQuery and RedundancyQuery optimized implementation. Keeping optimized properties separated per rule (instead of the union of all policy rules) Fixed handling HostEPs in optimized implementation. Signed-off-by: Tanya <[email protected]> * Fixed domain updating mechanism per rule (to avoid activating multiple times for the same rule, for example when a rule appears twice in a config). Signed-off-by: Tanya <[email protected]> * Fixed lint errors Signed-off-by: Tanya <[email protected]> * Enabled strongEquivalence optimized implementation. Signed-off-by: Tanya <[email protected]> * Implemented optimized ContainmentQuery. Commented out containment fullExplanation result comparison in tests, since optimized solution gives more accurate result that differs from the original expected result, and thus the test fails. Signed-off-by: Tanya <[email protected]> * Enabled optimized TwoContainmentQuery and PermitsQuery. Commented out twoWayContainment fullExplanation result comparison in tests, since optimized solution gives more accurate result that differs from the original expected result, and thus the tests fail. Signed-off-by: Tanya <[email protected]> * Fixed small inaccuracy in handling host endpoints in optimized solution. Adding docs Signed-off-by: Tanya <[email protected]> * Protecting optimized props policy members from direct access; accessing only by 'getter' methods, to ensure sync is called before access. Signed-off-by: Tanya <[email protected]> * Added implemented queries to run_all_tests.py Signed-off-by: Tanya <[email protected]> --------- Signed-off-by: Tanya <[email protected]>
1 parent fae6bc0 commit 59f6126

File tree

13 files changed

+169
-116
lines changed

13 files changed

+169
-116
lines changed

nca/NetworkConfig/NetworkConfigQuery.py

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,8 +1015,8 @@ def _txt_no_fw_rules_format_from_connections_dict(self, connections, peers, conn
10151015
:param PeerSet peers: the peers to consider for dot output
10161016
:param Union[str,None] connectivity_restriction: specify if connectivity is restricted to TCP / non-TCP , or not
10171017
:rtype: str
1018-
:return the connectivity map in txt_no_fw_rules format: the connections between peers excluding connections between
1019-
workload to itself (without grouping as fw-rules).
1018+
:return the connectivity map in txt_no_fw_rules format: the connections between peers excluding connections
1019+
between workload to itself (without grouping as fw-rules).
10201020
"""
10211021
conn_graph = self._get_conn_graph(connections, peers)
10221022
return conn_graph.get_connections_without_fw_rules_txt_format(connectivity_restriction)
@@ -1130,8 +1130,8 @@ def split_to_tcp_and_non_tcp_conns(conns):
11301130
@staticmethod
11311131
def convert_props_to_split_by_tcp(props):
11321132
"""
1133-
given the ConnectivityProperties properties set, convert it to two properties sets, one for TCP only, and the other
1134-
for non-TCP only.
1133+
given the ConnectivityProperties properties set, convert it to two properties sets, one for TCP only,
1134+
and the other for non-TCP only.
11351135
:param ConnectivityProperties props: properties describing allowed connections
11361136
:return: a tuple of the two properties sets: first for TCP, second for non-TCP
11371137
:rtype: tuple(ConnectivityProperties, ConnectivityProperties)
@@ -1222,6 +1222,35 @@ def filter_conns_by_input_or_internal_constraints(self, conns1, conns2):
12221222
res_conns2 = self.config2.filter_conns_by_peer_types(conns2, peers_to_compare) & conns_filter
12231223
return res_conns1, res_conns2
12241224

1225+
def _append_different_conns_to_list(self, conn_diff_props, different_conns_list, props_based_on_config1=True):
1226+
"""
1227+
Adds difference between config1 and config2 connectivities into the list of differences
1228+
:param ConnectivityProperties conn_diff_props: connectivity properties representing a difference
1229+
between config1 and config2 connections (or between config2 and config1 connections)
1230+
:param list different_conns_list: the list to add differences to
1231+
:param bool props_based_on_config1: whether conn_diff_props represent connections present in config1 but not in config2
1232+
(the value True) or connections present in config2 but not in config1 (the value False)
1233+
"""
1234+
no_conns = ConnectionSet()
1235+
for cube in conn_diff_props:
1236+
conn_cube = conn_diff_props.get_connectivity_cube(cube)
1237+
conns, src_peers, dst_peers = \
1238+
ConnectionSet.get_connection_set_and_peers_from_cube(conn_cube, self.config1.peer_container)
1239+
conns1 = conns if props_based_on_config1 else no_conns
1240+
conns2 = no_conns if props_based_on_config1 else conns
1241+
if self.output_config.fullExplanation:
1242+
if self.config1.optimized_run == 'true':
1243+
different_conns_list.append(PeersAndConnections(str(src_peers), str(dst_peers), conns1, conns2))
1244+
else: # 'debug': produce the same output format as in the original implementation (per peer pairs)
1245+
for src_peer in src_peers:
1246+
for dst_peer in dst_peers:
1247+
if src_peer != dst_peer:
1248+
different_conns_list.append(PeersAndConnections(str(src_peer), str(dst_peer),
1249+
conns1, conns2))
1250+
else:
1251+
different_conns_list.append(PeersAndConnections(src_peers.rep(), dst_peers.rep(), conns1, conns2))
1252+
return
1253+
12251254
@staticmethod
12261255
def clone_without_ingress(config):
12271256
"""
@@ -1291,33 +1320,6 @@ def check_equivalence_original(self, layer_name=None):
12911320
return QueryAnswer(True, self.name1 + ' and ' + self.name2 + ' are semantically equivalent.',
12921321
numerical_result=0)
12931322

1294-
def _append_different_conns_to_list(self, conn_props, different_conns_list, props_based_on_config1):
1295-
"""
1296-
Adds difference between config1 and config2 connectivities into the list of differences
1297-
:param ConnectivityProperties conn_props: connectivity properties representing a difference between config1 and config2
1298-
:param list different_conns_list: the list to add differences to
1299-
:param bool props_based_on_config1: whether conn_props represent connections present in config1 but not in config2
1300-
(the value True) or connections present in config2 but not in config1 (the value False)
1301-
"""
1302-
no_conns = ConnectionSet()
1303-
for cube in conn_props:
1304-
conn_cube = conn_props.get_connectivity_cube(cube)
1305-
conns, src_peers, dst_peers = \
1306-
ConnectionSet.get_connection_set_and_peers_from_cube(conn_cube, self.config1.peer_container)
1307-
conns1 = conns if props_based_on_config1 else no_conns
1308-
conns2 = no_conns if props_based_on_config1 else conns
1309-
if self.output_config.fullExplanation:
1310-
if self.config1.optimized_run == 'true':
1311-
different_conns_list.append(PeersAndConnections(str(src_peers), str(dst_peers), conns1, conns2))
1312-
else: # 'debug': produce the same output format as in the original implementation (per peer pairs)
1313-
for src_peer in src_peers:
1314-
for dst_peer in dst_peers:
1315-
if src_peer != dst_peer:
1316-
different_conns_list.append(PeersAndConnections(str(src_peer), str(dst_peer),
1317-
conns1, conns2))
1318-
else:
1319-
different_conns_list.append(PeersAndConnections(src_peers.rep(), dst_peers.rep(), conns1, conns2))
1320-
13211323
def check_equivalence_optimized(self, layer_name=None):
13221324
conn_props1 = self.config1.allowed_connections_optimized(layer_name)
13231325
conn_props2 = self.config2.allowed_connections_optimized(layer_name)
@@ -1722,6 +1724,13 @@ def exec(self, cmd_line_flag=False, only_captured=False):
17221724
return QueryAnswer(False, f'{self.name1} is not contained in {self.name2} ',
17231725
output_explanation=[final_explanation], numerical_result=0 if not cmd_line_flag else 1)
17241726

1727+
if self.config1.optimized_run == 'false':
1728+
return self.check_containment_original(cmd_line_flag, only_captured)
1729+
else:
1730+
return self.check_containment_optimized(cmd_line_flag, only_captured)
1731+
1732+
def check_containment_original(self, cmd_line_flag=False, only_captured=False):
1733+
config1_peers = self.config1.peer_container.get_all_peers_group(include_dns_entries=True)
17251734
peers_to_compare = config1_peers | self.disjoint_referenced_ip_blocks()
17261735
captured_pods = self.config1.get_captured_pods() | self.config2.get_captured_pods()
17271736
not_contained_list = []
@@ -1745,6 +1754,21 @@ def exec(self, cmd_line_flag=False, only_captured=False):
17451754
return QueryAnswer(True, self.name1 + ' is contained in ' + self.name2,
17461755
numerical_result=1 if not cmd_line_flag else 0)
17471756

1757+
def check_containment_optimized(self, cmd_line_flag=False, only_captured=False):
1758+
conn_props1 = self.config1.allowed_connections_optimized()
1759+
conn_props2 = self.config2.allowed_connections_optimized()
1760+
conns1, conns2 = self.filter_conns_by_input_or_internal_constraints(
1761+
conn_props1.allowed_conns if only_captured else conn_props1.all_allowed_conns,
1762+
conn_props2.all_allowed_conns)
1763+
if conns1.contained_in(conns2):
1764+
return QueryAnswer(True, self.name1 + ' is contained in ' + self.name2,
1765+
numerical_result=1 if not cmd_line_flag else 0)
1766+
1767+
conns1_not_in_conns2 = conns1 - conns2
1768+
different_conns_list = []
1769+
self._append_different_conns_to_list(conns1_not_in_conns2, different_conns_list)
1770+
return self._query_answer_with_relevant_explanation(sorted(different_conns_list), cmd_line_flag)
1771+
17481772
def _query_answer_with_relevant_explanation(self, explanation_list, cmd_line_flag):
17491773
output_result = f'{self.name1} is not contained in {self.name2}'
17501774
explanation_description = f'Connections allowed in {self.name1} which are not a subset of those in {self.name2}'

nca/Resources/CalicoNetworkPolicy.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,26 +87,26 @@ def _update_opt_props_by_order(self, is_ingress):
8787
for rule in self.ingress_rules if is_ingress else self.egress_rules:
8888
props = rule.optimized_props.copy()
8989
if rule.action == CalicoPolicyRule.ActionType.Allow:
90-
props -= self.optimized_deny_ingress_props if is_ingress else self.optimized_deny_egress_props
91-
props -= self.optimized_pass_ingress_props if is_ingress else self.optimized_pass_egress_props
90+
props -= self._optimized_deny_ingress_props if is_ingress else self._optimized_deny_egress_props
91+
props -= self._optimized_pass_ingress_props if is_ingress else self._optimized_pass_egress_props
9292
if is_ingress:
93-
self.optimized_allow_ingress_props |= props
93+
self._optimized_allow_ingress_props |= props
9494
else:
95-
self.optimized_allow_egress_props |= props
95+
self._optimized_allow_egress_props |= props
9696
elif rule.action == CalicoPolicyRule.ActionType.Deny:
97-
props -= self.optimized_allow_ingress_props if is_ingress else self.optimized_allow_egress_props
98-
props -= self.optimized_pass_ingress_props if is_ingress else self.optimized_pass_egress_props
97+
props -= self._optimized_allow_ingress_props if is_ingress else self._optimized_allow_egress_props
98+
props -= self._optimized_pass_ingress_props if is_ingress else self._optimized_pass_egress_props
9999
if is_ingress:
100-
self.optimized_deny_ingress_props |= props
100+
self._optimized_deny_ingress_props |= props
101101
else:
102-
self.optimized_deny_egress_props |= props
102+
self._optimized_deny_egress_props |= props
103103
elif rule.action == CalicoPolicyRule.ActionType.Pass:
104-
props -= self.optimized_allow_ingress_props if is_ingress else self.optimized_allow_egress_props
105-
props -= self.optimized_deny_ingress_props if is_ingress else self.optimized_deny_egress_props
104+
props -= self._optimized_allow_ingress_props if is_ingress else self._optimized_allow_egress_props
105+
props -= self._optimized_deny_ingress_props if is_ingress else self._optimized_deny_egress_props
106106
if is_ingress:
107-
self.optimized_pass_ingress_props |= props
107+
self._optimized_pass_ingress_props |= props
108108
else:
109-
self.optimized_pass_egress_props |= props
109+
self._optimized_pass_egress_props |= props
110110

111111
def sync_opt_props(self):
112112
"""
@@ -169,17 +169,16 @@ def allowed_connections_optimized(self, is_ingress):
169169
and the peer set of captured peers by this policy.
170170
:rtype: tuple (ConnectivityProperties, ConnectivityProperties, PeerSet)
171171
"""
172-
self.sync_opt_props()
173172
res_conns = OptimizedPolicyConnections()
174173
if is_ingress:
175-
res_conns.allowed_conns = self.optimized_allow_ingress_props.copy()
176-
res_conns.denied_conns = self.optimized_deny_ingress_props.copy()
177-
res_conns.pass_conns = self.optimized_pass_ingress_props.copy()
174+
res_conns.allowed_conns = self.optimized_allow_ingress_props().copy()
175+
res_conns.denied_conns = self.optimized_deny_ingress_props().copy()
176+
res_conns.pass_conns = self.optimized_pass_ingress_props().copy()
178177
res_conns.captured = self.selected_peers if self.affects_ingress else Peer.PeerSet()
179178
else:
180-
res_conns.allowed_conns = self.optimized_allow_egress_props.copy()
181-
res_conns.denied_conns = self.optimized_deny_egress_props.copy()
182-
res_conns.pass_conns = self.optimized_pass_egress_props.copy()
179+
res_conns.allowed_conns = self.optimized_allow_egress_props().copy()
180+
res_conns.denied_conns = self.optimized_deny_egress_props().copy()
181+
res_conns.pass_conns = self.optimized_pass_egress_props().copy()
183182
res_conns.captured = self.selected_peers if self.affects_egress else Peer.PeerSet()
184183
return res_conns
185184

nca/Resources/IngressPolicy.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def sync_opt_props(self):
7272
return
7373
self._init_opt_props()
7474
for rule in self.egress_rules:
75-
self.optimized_allow_egress_props |= rule.optimized_props
75+
self._optimized_allow_egress_props |= rule.optimized_props
7676
self.optimized_props_in_sync = True
7777

7878
def allowed_connections(self, from_peer, to_peer, is_ingress):
@@ -110,13 +110,12 @@ def allowed_connections_optimized(self, is_ingress):
110110
and the peer set of captured peers by this policy.
111111
:rtype: tuple (ConnectivityProperties, ConnectivityProperties, PeerSet)
112112
"""
113-
self.sync_opt_props()
114113
res_conns = OptimizedPolicyConnections()
115114
if is_ingress:
116115
res_conns.allowed_conns = ConnectivityProperties.make_empty_props()
117116
res_conns.captured = PeerSet()
118117
else:
119-
res_conns.allowed_conns = self.optimized_allow_egress_props.copy()
118+
res_conns.allowed_conns = self.optimized_allow_egress_props().copy()
120119
res_conns.captured = self.selected_peers if self.affects_egress else PeerSet()
121120
return res_conns
122121

nca/Resources/IstioNetworkPolicy.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ def sync_opt_props(self):
7878
self._init_opt_props()
7979
for rule in self.ingress_rules:
8080
if self.action == IstioNetworkPolicy.ActionType.Allow:
81-
self.optimized_allow_ingress_props |= rule.optimized_props
81+
self._optimized_allow_ingress_props |= rule.optimized_props
8282
elif self.action == IstioNetworkPolicy.ActionType.Deny:
83-
self.optimized_deny_ingress_props |= rule.optimized_props
83+
self._optimized_deny_ingress_props |= rule.optimized_props
8484

85-
self.optimized_allow_egress_props = ConnectivityProperties.get_all_conns_props_per_domain_peers()
85+
self._optimized_allow_egress_props = ConnectivityProperties.get_all_conns_props_per_domain_peers()
8686
self.optimized_props_in_sync = True
8787

8888
def allowed_connections(self, from_peer, to_peer, is_ingress):
@@ -122,15 +122,14 @@ def allowed_connections_optimized(self, is_ingress):
122122
and the peer set of captured peers by this policy.
123123
:rtype: tuple (ConnectivityProperties, ConnectivityProperties, PeerSet)
124124
"""
125-
self.sync_opt_props()
126125
res_conns = OptimizedPolicyConnections()
127126
if is_ingress:
128-
res_conns.allowed_conns = self.optimized_allow_ingress_props.copy()
129-
res_conns.denied_conns = self.optimized_deny_ingress_props.copy()
127+
res_conns.allowed_conns = self.optimized_allow_ingress_props().copy()
128+
res_conns.denied_conns = self.optimized_deny_ingress_props().copy()
130129
res_conns.captured = self.selected_peers
131130
else:
132-
res_conns.allowed_conns = self.optimized_allow_egress_props.copy()
133-
res_conns.denied_conns = self.optimized_deny_egress_props.copy()
131+
res_conns.allowed_conns = self.optimized_allow_egress_props().copy()
132+
res_conns.denied_conns = self.optimized_deny_egress_props().copy()
134133
res_conns.captured = PeerSet()
135134
return res_conns
136135

nca/Resources/IstioSidecar.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ def sync_opt_props(self):
5959
if self.optimized_props_in_sync:
6060
return
6161
self._init_opt_props()
62-
self.optimized_allow_ingress_props = ConnectivityProperties.get_all_conns_props_per_domain_peers()
62+
self._optimized_allow_ingress_props = ConnectivityProperties.get_all_conns_props_per_domain_peers()
6363
for rule in self.egress_rules:
64-
self.optimized_allow_egress_props |= rule.optimized_props
64+
self._optimized_allow_egress_props |= rule.optimized_props
6565
self.optimized_props_in_sync = True
6666

6767
def allowed_connections(self, from_peer, to_peer, is_ingress):
@@ -100,15 +100,14 @@ def allowed_connections(self, from_peer, to_peer, is_ingress):
100100
return PolicyConnections(True, allowed_conns=ConnectionSet())
101101

102102
def allowed_connections_optimized(self, is_ingress):
103-
self.sync_opt_props()
104103
res_conns = OptimizedPolicyConnections()
105104
if is_ingress:
106-
res_conns.allowed_conns = self.optimized_allow_ingress_props.copy()
107-
res_conns.denied_conns = self.optimized_deny_ingress_props.copy()
105+
res_conns.allowed_conns = self.optimized_allow_ingress_props().copy()
106+
res_conns.denied_conns = self.optimized_deny_ingress_props().copy()
108107
res_conns.captured = PeerSet()
109108
else:
110-
res_conns.allowed_conns = self.optimized_allow_egress_props.copy()
111-
res_conns.denied_conns = self.optimized_deny_egress_props.copy()
109+
res_conns.allowed_conns = self.optimized_allow_egress_props().copy()
110+
res_conns.denied_conns = self.optimized_deny_egress_props().copy()
112111
res_conns.captured = self.selected_peers if self.affects_egress else PeerSet()
113112
return res_conns
114113

nca/Resources/K8sNetworkPolicy.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ def sync_opt_props(self):
4848
return
4949
self._init_opt_props()
5050
for rule in self.ingress_rules:
51-
self.optimized_allow_ingress_props |= rule.optimized_props
51+
self._optimized_allow_ingress_props |= rule.optimized_props
5252
for rule in self.egress_rules:
53-
self.optimized_allow_egress_props |= rule.optimized_props
53+
self._optimized_allow_egress_props |= rule.optimized_props
5454
self.optimized_props_in_sync = True
5555

5656
def allowed_connections(self, from_peer, to_peer, is_ingress):
@@ -89,13 +89,12 @@ def allowed_connections_optimized(self, is_ingress):
8989
and the peer set of captured peers by this policy.
9090
:rtype: tuple (ConnectivityProperties, ConnectivityProperties, PeerSet)
9191
"""
92-
self.sync_opt_props()
9392
res_conns = OptimizedPolicyConnections()
9493
if is_ingress:
95-
res_conns.allowed_conns = self.optimized_allow_ingress_props.copy()
94+
res_conns.allowed_conns = self.optimized_allow_ingress_props().copy()
9695
res_conns.captured = self.selected_peers if self.affects_ingress else Peer.PeerSet()
9796
else:
98-
res_conns.allowed_conns = self.optimized_allow_egress_props.copy()
97+
res_conns.allowed_conns = self.optimized_allow_egress_props().copy()
9998
res_conns.captured = self.selected_peers if self.affects_egress else Peer.PeerSet()
10099
return res_conns
101100

0 commit comments

Comments
 (0)