Test Failed
Push — master ( b00b5f...8e950f )
by Beraldo
01:15
created

build.models.Path.__eq__()   A

Complexity

Conditions 3

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 4
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 12
1
"""Classes used in the main application."""
2
from datetime import datetime
3
from uuid import uuid4
4
5
import requests
6
7
from kytos.core import log
8
from kytos.core.common import EntityStatus, GenericEntity
9
from kytos.core.helpers import get_time, now
10
from kytos.core.interface import UNI
11
from napps.kytos.mef_eline import settings
12
13
14
class Path(list, GenericEntity):
15
    """Class to represent a Path."""
16
17
    def __init__(self, *args, **kwargs):
18
        """Create a path instance using links."""
19
        super().__init__(*args, **kwargs)
20
        self.links_cache = set(self)
21
22
    def __eq__(self, other=None):
23
        """Compare paths."""
24
        if not other or not isinstance(other, Path):
25
            return False
26
        return super().__eq__(other)
27
28
    def is_affected_by_link(self, link=None):
29
        """Verify if the current path is affected by link."""
30
        if not link:
31
            return False
32
        return link in self.links_cache
33
34
    @property
35
    def status(self):
36
        """Check for the  status of a path.
37
38
        If any link in this path is down, the path is considered down.
39
        """
40
        if not self:
41
            return EntityStatus.DISABLED
42
43
        for link in self:
44
            if link.status is not EntityStatus.UP:
45
                return link.status
46
        return EntityStatus.UP
47
48
    def as_dict(self):
49
        """Return list comprehension of links as_dict."""
50
        return [link.as_dict() for link in self if link]
51
52
53
class EVCBase(GenericEntity):
54
    """"Class to represent a circuit."""
55
56
    unique_attributes = ['name', 'uni_a', 'uni_z']
57
58
    def __init__(self, **kwargs):
59
        """Create an EVC instance with the provided parameters.
60
61
        Args:
62
            id(str): EVC identifier. Whether it's None an ID will be genereted.
63
            name: represents an EVC name.(Required)
64
            uni_a (UNI): Endpoint A for User Network Interface.(Required)
65
            uni_z (UNI): Endpoint Z for User Network Interface.(Required)
66
            start_date(datetime|str): Date when the EVC was registred.
67
                                      Default is now().
68
            end_date(datetime|str): Final date that the EVC will be fineshed.
69
                                    Default is None.
70
            bandwidth(int): Bandwidth used by EVC instance. Default is 0.
71
            primary_links(list): Primary links used by evc. Default is []
72
            backup_links(list): Backups links used by evc. Default is []
73
            current_path(list): Circuit being used at the moment if this is an
74
                                active circuit. Default is [].
75
            primary_path(list): primary circuit offered to user IF one or more
76
                                links were provided. Default is [].
77
            backup_path(list): backup circuit offered to the user IF one or
78
                               more links were provided. Default is [].
79
            dynamic_backup_path(bool): Enable computer backup path dynamically.
80
                                       Dafault is False.
81
            creation_time(datetime|str): datetime when the circuit should be
82
                                         activated. default is now().
83
            enabled(Boolean): attribute to indicate the operational state.
84
                              default is False.
85
            active(Boolean): attribute to Administrative state;
86
                             default is False.
87
            owner(str): The EVC owner. Default is None.
88
            priority(int): Service level provided in the request. Default is 0.
89
90
        Raises:
91
            ValueError: raised when object attributes are invalid.
92
93
        """
94
        self._validate(**kwargs)
95
        super().__init__()
96
97
        # required attributes
98
        self._id = kwargs.get('id', uuid4().hex)
99
        self.uni_a = kwargs.get('uni_a')
100
        self.uni_z = kwargs.get('uni_z')
101
        self.name = kwargs.get('name')
102
103
        # optional attributes
104
        self.start_date = get_time(kwargs.get('start_date')) or now()
105
        self.end_date = get_time(kwargs.get('end_date')) or None
106
107
        self.bandwidth = kwargs.get('bandwidth', 0)
108
        self.primary_links = Path(kwargs.get('primary_links', []))
109
        self.backup_links = Path(kwargs.get('backup_links', []))
110
        self.current_path = Path(kwargs.get('current_path', []))
111
        self.primary_path = Path(kwargs.get('primary_path', []))
112
        self.backup_path = Path(kwargs.get('backup_path', []))
113
        self.dynamic_backup_path = kwargs.get('dynamic_backup_path', False)
114
        self.creation_time = get_time(kwargs.get('creation_time')) or now()
115
        self.owner = kwargs.get('owner', None)
116
        self.priority = kwargs.get('priority', 0)
117
        self.circuit_scheduler = kwargs.get('circuit_scheduler', [])
118
119
        if kwargs.get('active', False):
120
            self.activate()
121
        else:
122
            self.deactivate()
123
124
        if kwargs.get('enabled', False):
125
            self.enable()
126
        else:
127
            self.disable()
128
129
        # datetime of user request for a EVC (or datetime when object was
130
        # created)
131
        self.request_time = kwargs.get('request_time', now())
132
        # dict with the user original request (input)
133
        self._requested = kwargs
134
135
    def update(self, **kwargs):
136
        """Update evc attributes.
137
138
        This method will raises an error trying to change the following
139
        attributes: [name, uni_a and uni_z]
140
141
        Raises:
142
            ValueError: message with error detail.
143
144
        """
145
        for attribute, value in kwargs.items():
146
            if attribute in self.unique_attributes:
147
                raise ValueError(f'{attribute} can\'t be be updated.')
148
            if hasattr(self, attribute):
149
                setattr(self, attribute, value)
150
            else:
151
                raise ValueError(f'The attribute "{attribute}" is invalid.')
152
153
    def __repr__(self):
154
        """Repr method."""
155
        return f"EVC({self._id}, {self.name})"
156
157
    def _validate(self, **kwargs):
158
        """Do Basic validations.
159
160
        Verify required attributes: name, uni_a, uni_z
161
        Verify if the attributes uni_a and uni_z are valid.
162
163
        Raises:
164
            ValueError: message with error detail.
165
166
        """
167
        for attribute in self.unique_attributes:
168
169
            if attribute not in kwargs:
170
                raise ValueError(f'{attribute} is required.')
171
172
            if 'uni' in attribute:
173
                uni = kwargs.get(attribute)
174
                if not isinstance(uni, UNI):
175
                    raise ValueError(f'{attribute} is an invalid UNI.')
176
177
                elif not uni.is_valid():
178
                    tag = uni.user_tag.value
179
                    message = f'VLAN tag {tag} is not available in {attribute}'
180
                    raise ValueError(message)
181
182
    def __eq__(self, other):
183
        """Override the default implementation."""
184
        if not isinstance(other, EVC):
185
            return False
186
187
        attrs_to_compare = ['name', 'uni_a', 'uni_z', 'owner', 'bandwidth']
188
        for attribute in attrs_to_compare:
189
            if getattr(other, attribute) != getattr(self, attribute):
190
                return False
191
        return True
192
193
    def as_dict(self):
194
        """Return a dictionary representing an EVC object."""
195
        evc_dict = {"id": self.id, "name": self.name,
196
                    "uni_a": self.uni_a.as_dict(),
197
                    "uni_z": self.uni_z.as_dict()}
198
199
        time_fmt = "%Y-%m-%dT%H:%M:%S"
200
201
        evc_dict["start_date"] = self.start_date
202
        if isinstance(self.start_date, datetime):
203
            evc_dict["start_date"] = self.start_date.strftime(time_fmt)
204
205
        evc_dict["end_date"] = self.end_date
206
        if isinstance(self.end_date, datetime):
207
            evc_dict["end_date"] = self.end_date.strftime(time_fmt)
208
209
        evc_dict['bandwidth'] = self.bandwidth
210
        evc_dict['primary_links'] = self.primary_links.as_dict()
211
        evc_dict['backup_links'] = self.backup_links.as_dict()
212
        evc_dict['current_path'] = self.current_path.as_dict()
213
        evc_dict['primary_path'] = self.primary_path.as_dict()
214
        evc_dict['backup_path'] = self.backup_path.as_dict()
215
        evc_dict['dynamic_backup_path'] = self.dynamic_backup_path
216
217
        if self._requested:
218
            request_dict = self._requested.copy()
219
            request_dict['uni_a'] = request_dict['uni_a'].as_dict()
220
            request_dict['uni_z'] = request_dict['uni_z'].as_dict()
221
            evc_dict['_requested'] = request_dict
222
223
        evc_dict["request_time"] = self.request_time
224
        if isinstance(self.request_time, datetime):
225
            evc_dict["request_time"] = self.request_time.strftime(time_fmt)
226
227
        time = self.creation_time.strftime(time_fmt)
228
        evc_dict['creation_time'] = time
229
230
        evc_dict['owner'] = self.owner
231
        evc_dict['circuit_scheduler'] = self.circuit_scheduler
232
        evc_dict['active'] = self.is_active()
233
        evc_dict['enabled'] = self.is_enabled()
234
        evc_dict['priority'] = self.priority
235
236
        return evc_dict
237
238
    @property
239
    def id(self):  # pylint: disable=invalid-name
240
        """Return this EVC's ID."""
241
        return self._id
242
243
244
class EVCDeploy(EVCBase):
245
    """Class to handle the deploy procedures."""
246
247
    def create(self):
248
        """Create a EVC."""
249
        pass
250
251
    def discover_new_path(self):
252
        """Discover a new path to satisfy this circuit and deploy."""
253
        pass
254
255
    def change_path(self, path):
256
        """Change EVC path."""
257
        pass
258
259
    def reprovision(self):
260
        """Force the EVC (re-)provisioning."""
261
        pass
262
263
    def remove(self):
264
        """Remove EVC path."""
265
        pass
266
267
    @staticmethod
268
    def choose_vlans(path=None):
269
        """Choose the VLANs to be used for the circuit."""
270
        for link in path:
271
            tag = link.get_next_available_tag()
272
            link.use_tag(tag)
273
            link.add_metadata('s_vlan', tag)
274
275
    @staticmethod
276
    def links_zipped(path=None):
277
        """Return an iterator which yields pairs of links in order."""
278
        if not path:
279
            return []
280
        return zip(path[:-1], path[1:])
281
282
    def should_deploy(self, path=None):
283
        """Verify if the circuit should be deployed."""
284
        if not path:
285
            log.debug("Path is empty.")
286
            return False
287
288
        if not self.is_enabled():
289
            log.debug(f'{self} is disabled.')
290
            return False
291
292
        if not self.is_active():
293
            log.debug(f'{self} will be deployed.')
294
            return True
295
296
        return False
297
298
    def deploy(self, path=None):
299
        """Install the flows for this circuit.
300
301
        Procedures to deploy:
302
303
        1. Decide if will deploy "path" or discover a new path
304
        2. Choose vlan
305
        3. Install NNI flows
306
        4. Install UNI flows
307
        5. Activate
308
        6. Update current_path
309
        7. Update links caches(primary, current, backup)
310
311
        """
312
        if not self.should_deploy(path):
313
            return
314
315
        self.choose_vlans(path)
316
        self.install_nni_flows(path)
317
        self.install_uni_flows(path)
318
        self.activate()
319
        log.info(f"{self} was deployed.")
320
321
    def install_nni_flows(self, path=None):
322
        """Install NNI flows."""
323
        for incoming, outcoming in self.links_zipped(path):
324
            in_vlan = incoming.get_metadata('s_vlan').value
325
            out_vlan = outcoming.get_metadata('s_vlan').value
326
327
            flows = []
328
            # Flow for one direction
329
            flows.append(self.prepare_nni_flow(incoming.endpoint_b,
330
                                               outcoming.endpoint_a,
331
                                               in_vlan, out_vlan))
332
333
            # Flow for the other direction
334
            flows.append(self.prepare_nni_flow(outcoming.endpoint_a,
335
                                               incoming.endpoint_b,
336
                                               out_vlan, in_vlan))
337
            self.send_flow_mods(incoming.endpoint_b.switch, flows)
338
339
    def install_uni_flows(self, path=None):
340
        """Install UNI flows."""
341
        if not path:
342
            log.info('install uni flows without path.')
343
            return
344
345
        # Determine VLANs
346
        in_vlan_a = self.uni_a.user_tag.value if self.uni_a.user_tag else None
347
        out_vlan_a = path[0].get_metadata('s_vlan').value
348
349
        in_vlan_z = self.uni_z.user_tag.value if self.uni_z.user_tag else None
350
        out_vlan_z = path[-1].get_metadata('s_vlan').value
351
352
        # Flows for the first UNI
353
        flows_a = []
354
355
        # Flow for one direction, pushing the service tag
356
        push_flow = self.prepare_push_flow(self.uni_a.interface,
357
                                           path[0].endpoint_a,
358
                                           in_vlan_a, out_vlan_a, in_vlan_z)
359
        flows_a.append(push_flow)
360
361
        # Flow for the other direction, popping the service tag
362
        pop_flow = self.prepare_pop_flow(path[0].endpoint_a,
363
                                         self.uni_a.interface, out_vlan_a)
364
        flows_a.append(pop_flow)
365
366
        self.send_flow_mods(self.uni_a.interface.switch, flows_a)
367
368
        # Flows for the second UNI
369
        flows_z = []
370
371
        # Flow for one direction, pushing the service tag
372
        push_flow = self.prepare_push_flow(self.uni_z.interface,
373
                                           path[-1].endpoint_b,
374
                                           in_vlan_z, out_vlan_z, in_vlan_a)
375
        flows_z.append(push_flow)
376
377
        # Flow for the other direction, popping the service tag
378
        pop_flow = self.prepare_pop_flow(path[-1].endpoint_b,
379
                                         self.uni_z.interface, out_vlan_z)
380
        flows_z.append(pop_flow)
381
382
        self.send_flow_mods(self.uni_z.interface.switch, flows_z)
383
384
    @staticmethod
385
    def send_flow_mods(switch, flow_mods):
386
        """Send a flow_mod list to a specific switch."""
387
        endpoint = "%s/flows/%s" % (settings.MANAGER_URL, switch.id)
388
389
        data = {"flows": flow_mods}
390
        requests.post(endpoint, json=data)
391
392
    @staticmethod
393
    def prepare_flow_mod(in_interface, out_interface):
394
        """Prepare a common flow mod."""
395
        default_action = {"action_type": "output",
396
                          "port": out_interface.port_number}
397
398
        flow_mod = {"match": {"in_port": in_interface.port_number},
399
                    "actions": [default_action]}
400
401
        return flow_mod
402
403
    def prepare_nni_flow(self, in_interface, out_interface, in_vlan, out_vlan):
404
        """Create NNI flows."""
405
        flow_mod = self.prepare_flow_mod(in_interface, out_interface)
406
        flow_mod['match']['dl_vlan'] = in_vlan
407
408
        new_action = {"action_type": "set_vlan",
409
                      "vlan_id": out_vlan}
410
        flow_mod["actions"].insert(0, new_action)
411
412
        return flow_mod
413
414
    def prepare_push_flow(self, *args):
415
        """Prepare push flow.
416
417
        Arguments:
418
            in_interface(str): Interface input.
419
            out_interface(str): Interface output.
420
            in_vlan(str): Vlan input.
421
            out_vlan(str): Vlan output.
422
            new_in_vlan(str): Interface input.
423
424
        Return:
425
            dict: An python dictionary representing a FlowMod
426
427
        """
428
        # assign all arguments
429
        in_interface, out_interface, in_vlan, out_vlan, new_in_vlan = args
430
431
        flow_mod = self.prepare_flow_mod(in_interface, out_interface)
432
        flow_mod['match']['dl_vlan'] = in_vlan
433
434
        new_action = {"action_type": "set_vlan",
435
                      "vlan_id": out_vlan}
436
        flow_mod["actions"].insert(0, new_action)
437
438
        new_action = {"action_type": "push_vlan",
439
                      "tag_type": "s"}
440
        flow_mod["actions"].insert(0, new_action)
441
442
        new_action = {"action_type": "set_vlan",
443
                      "vlan_id": new_in_vlan}
444
        flow_mod["actions"].insert(0, new_action)
445
446
        return flow_mod
447
448
    def prepare_pop_flow(self, in_interface, out_interface, in_vlan):
449
        """Prepare pop flow."""
450
        flow_mod = self.prepare_flow_mod(in_interface, out_interface)
451
        flow_mod['match']['dl_vlan'] = in_vlan
452
        new_action = {"action_type": "pop_vlan"}
453
        flow_mod["actions"].insert(0, new_action)
454
        return flow_mod
455
456
457
class EVC(EVCDeploy):
458
    """Class that represents a E-Line Virtual Connection."""
459
460
    pass
461