|
1
|
|
|
"""Utility functions to be used in this Napp""" |
|
2
|
|
|
|
|
3
|
1 |
|
import requests |
|
4
|
1 |
|
from kytos.core import KytosEvent |
|
5
|
1 |
|
from napps.amlight.sdntrace_cp import settings |
|
6
|
|
|
|
|
7
|
|
|
|
|
8
|
1 |
|
def get_stored_flows(dpids: list = None, state: str = "installed"): |
|
9
|
|
|
"""Get stored flows from flow_manager napps.""" |
|
10
|
1 |
|
api_url = f'{settings.FLOW_MANAGER_URL}/stored_flows' |
|
11
|
1 |
|
if dpids: |
|
12
|
|
|
str_dpids = '' |
|
13
|
|
|
for dpid in dpids: |
|
14
|
|
|
str_dpids += f'&dpid={dpid}' |
|
15
|
|
|
api_url += '/?'+str_dpids[1:] |
|
16
|
1 |
|
if state: |
|
17
|
1 |
|
char = '&' if dpids else '/?' |
|
18
|
1 |
|
api_url += char+f'state={state}' |
|
19
|
1 |
|
result = requests.get(api_url) |
|
20
|
1 |
|
flows_from_manager = result.json() |
|
21
|
1 |
|
return flows_from_manager |
|
22
|
|
|
|
|
23
|
|
|
|
|
24
|
1 |
|
def convert_entries(entries): |
|
25
|
|
|
""" Transform entries dictionary in a plain dictionary suitable for |
|
26
|
|
|
matching |
|
27
|
|
|
|
|
28
|
|
|
:param entries: dict |
|
29
|
|
|
:return: plain dict |
|
30
|
|
|
""" |
|
31
|
1 |
|
new_entries = {} |
|
32
|
1 |
|
for entry in entries['trace'].values(): |
|
33
|
1 |
|
for field, value in entry.items(): |
|
34
|
1 |
|
new_entries[field] = value |
|
35
|
1 |
|
if 'dl_vlan' in new_entries: |
|
36
|
1 |
|
new_entries['dl_vlan'] = [new_entries['dl_vlan']] |
|
37
|
1 |
|
return new_entries |
|
38
|
|
|
|
|
39
|
|
|
|
|
40
|
1 |
|
def convert_list_entries(entries): |
|
41
|
|
|
""" Transform a list of entries dictionary in a list |
|
42
|
|
|
of plain dictionary suitable for matching |
|
43
|
|
|
:param entries: list(dict) |
|
44
|
|
|
:return: list(plain dict) |
|
45
|
|
|
""" |
|
46
|
1 |
|
new_entries = [] |
|
47
|
1 |
|
for entry in entries: |
|
48
|
1 |
|
new_entry = convert_entries(entry) |
|
49
|
1 |
|
if new_entry: |
|
50
|
1 |
|
new_entries.append(new_entry) |
|
51
|
1 |
|
return new_entries |
|
52
|
|
|
|
|
53
|
|
|
|
|
54
|
1 |
|
def find_endpoint(switch, port): |
|
55
|
|
|
""" Find where switch/port is connected. If it is another switch, |
|
56
|
|
|
returns the interface it is connected to, otherwise returns None """ |
|
57
|
|
|
|
|
58
|
1 |
|
interface = switch.get_interface_by_port_no(port) |
|
59
|
1 |
|
if interface.link: |
|
60
|
1 |
|
if interface == interface.link.endpoint_a: |
|
61
|
1 |
|
return interface.link.endpoint_b |
|
62
|
1 |
|
return interface.link.endpoint_a |
|
63
|
1 |
|
return None |
|
64
|
|
|
|
|
65
|
|
|
|
|
66
|
1 |
|
def _prepare_json(trace_result): |
|
67
|
|
|
"""Auxiliar function to return the json for REST call.""" |
|
68
|
1 |
|
result = [] |
|
69
|
1 |
|
for trace_step in trace_result: |
|
70
|
1 |
|
result.append(trace_step['in']) |
|
71
|
1 |
|
if result: |
|
72
|
1 |
|
result[-1]["out"] = trace_result[-1].get("out") |
|
73
|
1 |
|
return result |
|
74
|
|
|
|
|
75
|
|
|
|
|
76
|
1 |
|
def prepare_json(trace_result): |
|
77
|
|
|
"""Prepare return json for REST call.""" |
|
78
|
1 |
|
result = [] |
|
79
|
1 |
|
if trace_result and isinstance(trace_result[0], list): |
|
80
|
1 |
|
for trace in trace_result: |
|
81
|
1 |
|
result.append(_prepare_json(trace)) |
|
82
|
|
|
else: |
|
83
|
1 |
|
result = _prepare_json(trace_result) |
|
84
|
1 |
|
return {'result': result} |
|
85
|
|
|
|
|
86
|
|
|
|
|
87
|
1 |
|
def format_result(trace): |
|
88
|
|
|
"""Format the result for automate circuit finding""" |
|
89
|
1 |
|
result = [] |
|
90
|
1 |
|
for step in trace: |
|
91
|
1 |
|
new_result = {'dpid': step['in']['dpid'], |
|
92
|
|
|
'in_port': step['in']['port']} |
|
93
|
1 |
|
if 'out' in step: |
|
94
|
1 |
|
new_result.update({'out_port': step['out']['port']}) |
|
95
|
1 |
|
if 'vlan' in step['out']: |
|
96
|
1 |
|
new_result.update({'out_vlan': step['out']['vlan']}) |
|
97
|
1 |
|
if 'vlan' in step['in']: |
|
98
|
1 |
|
new_result.update({'in_vlan': step['in']['vlan']}) |
|
99
|
1 |
|
result.append(new_result) |
|
100
|
1 |
|
return result |
|
101
|
|
|
|
|
102
|
|
|
|
|
103
|
1 |
|
def clean_circuits(circuits, controller): |
|
104
|
|
|
"""Remove sub-circuits.""" |
|
105
|
1 |
|
cleaned_circuits = [] |
|
106
|
1 |
|
event = KytosEvent(name='amlight/kytos_courier.slack_send') |
|
107
|
1 |
|
content = { |
|
108
|
|
|
'channel': settings.SLACK_CHANNEL, |
|
109
|
|
|
'source': 'amlight/sdntrace_cp' |
|
110
|
|
|
} |
|
111
|
1 |
|
for circuit in circuits: |
|
112
|
1 |
|
sub = False |
|
113
|
1 |
|
for other in circuits: |
|
114
|
1 |
|
if circuit['circuit'] == other['circuit']: |
|
115
|
1 |
|
continue |
|
116
|
1 |
|
sub = True |
|
117
|
1 |
|
for step in circuit['circuit']: |
|
118
|
1 |
|
if step not in other['circuit']: |
|
119
|
1 |
|
sub = False |
|
120
|
1 |
|
break |
|
121
|
1 |
|
if sub: |
|
122
|
1 |
|
break |
|
123
|
1 |
|
if not sub: |
|
124
|
1 |
|
cleaned_circuits.append(circuit) |
|
125
|
|
|
|
|
126
|
1 |
|
for circuit in cleaned_circuits: |
|
127
|
1 |
|
has_return = False |
|
128
|
1 |
|
for other in cleaned_circuits: |
|
129
|
1 |
|
if _compare_endpoints(circuit['circuit'][0], |
|
130
|
|
|
other['circuit'][-1]) \ |
|
131
|
|
|
and _compare_endpoints(circuit['circuit'][-1], |
|
132
|
|
|
other['circuit'][0]): |
|
133
|
|
|
has_return = True |
|
134
|
1 |
|
if not has_return: |
|
135
|
1 |
|
content['m_body'] = f"Circuit {circuit['circuit']} has no way back" |
|
136
|
1 |
|
event.content['message'] = content |
|
137
|
1 |
|
controller.buffers.app.put(event) |
|
138
|
1 |
|
return cleaned_circuits |
|
139
|
|
|
|
|
140
|
|
|
|
|
141
|
|
|
# pylint: disable=too-many-return-statements |
|
142
|
1 |
|
def _compare_endpoints(endpoint1, endpoint2): |
|
143
|
1 |
|
if endpoint1['dpid'] != endpoint2['dpid']: |
|
144
|
1 |
|
return False |
|
145
|
1 |
|
if ( |
|
146
|
|
|
'in_port' not in endpoint1 |
|
147
|
|
|
or 'out_port' not in endpoint2 |
|
148
|
|
|
or endpoint1['in_port'] != endpoint2['out_port'] |
|
149
|
|
|
): |
|
150
|
1 |
|
return False |
|
151
|
1 |
|
if 'in_vlan' in endpoint1 and 'out_vlan' in endpoint2: |
|
152
|
1 |
|
if endpoint1['in_vlan'] != endpoint2['out_vlan']: |
|
153
|
1 |
|
return False |
|
154
|
1 |
|
elif 'in_vlan' in endpoint1 or 'out_vlan' in endpoint2: |
|
155
|
1 |
|
return False |
|
156
|
1 |
|
if 'out_vlan' in endpoint1 and 'in_vlan' in endpoint2: |
|
157
|
1 |
|
if endpoint1['out_vlan'] != endpoint2['in_vlan']: |
|
158
|
1 |
|
return False |
|
159
|
1 |
|
elif 'out_vlan' in endpoint1 or 'in_vlan' in endpoint2: |
|
160
|
1 |
|
return False |
|
161
|
1 |
|
return True |
|
162
|
|
|
|
|
163
|
|
|
|
|
164
|
1 |
|
def convert_vlan(value): |
|
165
|
|
|
"""Auxiliar function to calculate dl_vlan""" |
|
166
|
1 |
|
if isinstance(value, int): |
|
167
|
1 |
|
return value, 4095 |
|
168
|
1 |
|
value, mask = map(int, value.split('/')) |
|
169
|
1 |
|
return value, mask |
|
170
|
|
|
|
|
171
|
|
|
|
|
172
|
1 |
|
def match_field_dl_vlan(value, field_flow): |
|
173
|
|
|
""" Verify match in dl_vlan. |
|
174
|
|
|
value only takes an int in range [1,4095]. |
|
175
|
|
|
0 is not allowed for value. """ |
|
176
|
1 |
|
if not value: |
|
177
|
1 |
|
return field_flow == 0 |
|
178
|
1 |
|
value_flow, mask_flow = convert_vlan(field_flow) |
|
179
|
|
|
return value & (mask_flow & 4095) == value_flow & (mask_flow & 4095) |
|
180
|
|
|
|