Passed
Pull Request — master (#95)
by
unknown
02:53
created

build.utils.get_stored_flows()   B

Complexity

Conditions 6

Size

Total Lines 17
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7.8984

Importance

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