Test Failed
Push — master ( dba1ae...723a1d )
by Antonio
04:50 queued 13s
created

build.models.evc   F

Complexity

Total Complexity 146

Size/Duplication

Total Lines 873
Duplicated Lines 5.38 %

Importance

Changes 0
Metric Value
eloc 509
dl 47
loc 873
rs 2
c 0
b 0
f 0
wmc 146

48 Methods

Rating   Name   Duplication   Size   Complexity  
A EVCDeploy.link_affected_by_interface() 0 3 1
A EVCDeploy.is_backup_path_affected_by_link() 0 3 1
A EVCDeploy.is_using_backup_path() 0 3 1
A EVCBase.sync() 0 4 1
A LinkProtection.is_using_dynamic_path() 0 8 5
A EVCDeploy.remove() 0 6 1
A LinkProtection.is_affected_by_link() 0 3 1
C EVCDeploy.deploy_to_path() 0 54 10
A EVCDeploy._prepare_nni_flow() 0 12 1
A EVCDeploy.run_sdntrace() 0 18 2
A EVCDeploy._install_uni_flows() 0 50 4
A EVCDeploy.is_primary_path_affected_by_link() 0 3 1
A EVCBase.id() 0 4 1
A EVCDeploy.get_path_status() 0 13 4
B EVCDeploy.deploy_to_backup_path() 0 26 6
A EVCDeploy.reprovision() 0 2 1
A EVCDeploy._prepare_push_flow() 32 32 2
A EVCDeploy.links_zipped() 0 6 2
A EVCDeploy.is_using_dynamic_path() 0 8 5
B EVCDeploy.remove_current_flows() 0 26 5
A LinkProtection.is_using_primary_path() 0 3 1
B EVCDeploy.check_traces() 0 22 6
C EVCBase.update() 0 41 10
A EVCDeploy._send_flow_mods() 0 16 2
A EVCDeploy.create() 0 2 1
A LinkProtection.is_using_backup_path() 0 3 1
C LinkProtection.handle_link_up() 0 36 9
A EVCBase.__repr__() 0 3 1
B EVCBase._validate() 0 24 6
A EVCDeploy.should_deploy() 0 15 4
A EVCBase.as_dict() 0 50 4
A EVCDeploy.get_cookie() 0 3 1
A EVCDeploy.deploy_to_primary_path() 0 14 3
A EVCDeploy.deploy() 0 16 4
A EVCDeploy._prepare_pop_flow() 15 15 2
A LinkProtection.deploy_to() 0 10 3
A EVCBase.__eq__() 0 10 4
B EVCBase.__init__() 0 93 3
A EVCBase.archive() 0 3 1
A EVCBase.shares_uni() 0 6 3
B EVCDeploy._install_direct_uni_flows() 0 35 7
A EVCDeploy.is_affected_by_link() 0 3 1
A EVCDeploy.is_using_primary_path() 0 3 1
A EVCDeploy._install_nni_flows() 0 19 2
B LinkProtection.handle_link_down() 0 25 6
A EVCDeploy._prepare_flow_mod() 0 16 3
A EVCDeploy.discover_new_paths() 0 3 1
A EVCDeploy.change_path() 0 2 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like build.models.evc often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Classes used in the main application."""
2
from datetime import datetime
3
from threading import Lock
4
from uuid import uuid4
5
6
import requests
7
from glom import glom
8
9
from kytos.core import log
10
from kytos.core.common import EntityStatus, GenericEntity
11
from kytos.core.exceptions import KytosNoTagAvailableError
12
from kytos.core.helpers import get_time, now
13
from kytos.core.interface import UNI
14
from napps.kytos.mef_eline import settings
15
from napps.kytos.mef_eline.exceptions import FlowModException, InvalidPath
16
from napps.kytos.mef_eline.storehouse import StoreHouse
17
from napps.kytos.mef_eline.utils import compare_endpoint_trace, emit_event
18
from .path import Path, DynamicPathManager
19
20
21
class EVCBase(GenericEntity):
22
    """Class to represent a circuit."""
23
24
    read_only_attributes = [
25
        'creation_time', 'active', 'current_path',
26
        '_id', 'archived'
27
    ]
28
    attributes_requiring_redeploy = [
29
        'primary_path', 'backup_path', 'dynamic_backup_path', 'queue_id',
30
        'priority'
31
    ]
32
    required_attributes = ['name', 'uni_a', 'uni_z']
33
34
    def __init__(self, controller, **kwargs):
35
        """Create an EVC instance with the provided parameters.
36
37
        Args:
38
            id(str): EVC identifier. Whether it's None an ID will be genereted.
39
                     Only the first 14 bytes passed will be used.
40
            name: represents an EVC name.(Required)
41
            uni_a (UNI): Endpoint A for User Network Interface.(Required)
42
            uni_z (UNI): Endpoint Z for User Network Interface.(Required)
43
            start_date(datetime|str): Date when the EVC was registred.
44
                                      Default is now().
45
            end_date(datetime|str): Final date that the EVC will be fineshed.
46
                                    Default is None.
47
            bandwidth(int): Bandwidth used by EVC instance. Default is 0.
48
            primary_links(list): Primary links used by evc. Default is []
49
            backup_links(list): Backups links used by evc. Default is []
50
            current_path(list): Circuit being used at the moment if this is an
51
                                active circuit. Default is [].
52
            primary_path(list): primary circuit offered to user IF one or more
53
                                links were provided. Default is [].
54
            backup_path(list): backup circuit offered to the user IF one or
55
                               more links were provided. Default is [].
56
            dynamic_backup_path(bool): Enable computer backup path dynamically.
57
                                       Dafault is False.
58
            creation_time(datetime|str): datetime when the circuit should be
59
                                         activated. default is now().
60
            enabled(Boolean): attribute to indicate the administrative state;
61
                              default is False.
62
            active(Boolean): attribute to indicate the operational state;
63
                             default is False.
64
            archived(Boolean): indicate the EVC has been deleted and is
65
                               archived; default is False.
66
            owner(str): The EVC owner. Default is None.
67
            priority(int): Service level provided in the request. Default is 0.
68
69
        Raises:
70
            ValueError: raised when object attributes are invalid.
71
72
        """
73
        self._validate(**kwargs)
74
        super().__init__()
75
76
        # required attributes
77
        self._id = kwargs.get('id', uuid4().hex)[:14]
78
        self.uni_a = kwargs.get('uni_a')
79
        self.uni_z = kwargs.get('uni_z')
80
        self.name = kwargs.get('name')
81
82
        # optional attributes
83
        self.start_date = get_time(kwargs.get('start_date')) or now()
84
        self.end_date = get_time(kwargs.get('end_date')) or None
85
        self.queue_id = kwargs.get('queue_id', None)
86
87
        self.bandwidth = kwargs.get('bandwidth', 0)
88
        self.primary_links = Path(kwargs.get('primary_links', []))
89
        self.backup_links = Path(kwargs.get('backup_links', []))
90
        self.current_path = Path(kwargs.get('current_path', []))
91
        self.primary_path = Path(kwargs.get('primary_path', []))
92
        self.backup_path = Path(kwargs.get('backup_path', []))
93
        self.dynamic_backup_path = kwargs.get('dynamic_backup_path', False)
94
        self.creation_time = get_time(kwargs.get('creation_time')) or now()
95
        self.owner = kwargs.get('owner', None)
96
        self.priority = kwargs.get('priority', -1)
97
        self.circuit_scheduler = kwargs.get('circuit_scheduler', [])
98
99
        self.current_links_cache = set()
100
        self.primary_links_cache = set()
101
        self.backup_links_cache = set()
102
103
        self.lock = Lock()
104
105
        self.archived = kwargs.get('archived', False)
106
107
        self.metadata = kwargs.get('metadata', {})
108
109
        self._storehouse = StoreHouse(controller)
110
        self._controller = controller
111
112
        if kwargs.get('active', False):
113
            self.activate()
114
        else:
115
            self.deactivate()
116
117
        if kwargs.get('enabled', False):
118
            self.enable()
119
        else:
120
            self.disable()
121
122
        # datetime of user request for a EVC (or datetime when object was
123
        # created)
124
        self.request_time = kwargs.get('request_time', now())
125
        # dict with the user original request (input)
126
        self._requested = kwargs
127
128
    def sync(self):
129
        """Sync this EVC in the storehouse."""
130
        self._storehouse.save_evc(self)
131
        log.info(f'EVC {self.id} was synced to the storehouse.')
132
133
    def update(self, **kwargs):
134
        """Update evc attributes.
135
136
        This method will raises an error trying to change the following
137
        attributes: [name, uni_a and uni_z]
138
139
        Returns:
140
            the values for enable and a redeploy attribute, if exists and None
141
            otherwise
142
        Raises:
143
            ValueError: message with error detail.
144
145
        """
146
        enable, redeploy = (None, None)
147
        uni_a = kwargs.get('uni_a') or self.uni_a
148
        uni_z = kwargs.get('uni_z') or self.uni_z
149
        for attribute, value in kwargs.items():
150
            if attribute in self.read_only_attributes:
151
                raise ValueError(f'{attribute} can\'t be updated.')
152
            if not hasattr(self, attribute):
153
                raise ValueError(f'The attribute "{attribute}" is invalid.')
154
            if attribute in ('primary_path', 'backup_path'):
155
                try:
156
                    value.is_valid(uni_a.interface.switch,
157
                                   uni_z.interface.switch)
158
                except InvalidPath as exception:
159
                    raise ValueError(f'{attribute} is not a '
160
                                     f'valid path: {exception}')
161
        for attribute, value in kwargs.items():
162
            if attribute in ('enable', 'enabled'):
163
                if value:
164
                    self.enable()
165
                else:
166
                    self.disable()
167
                enable = value
168
            else:
169
                setattr(self, attribute, value)
170
                if attribute in self.attributes_requiring_redeploy:
171
                    redeploy = value
172
        self.sync()
173
        return enable, redeploy
174
175
    def __repr__(self):
176
        """Repr method."""
177
        return f"EVC({self._id}, {self.name})"
178
179
    def _validate(self, **kwargs):
180
        """Do Basic validations.
181
182
        Verify required attributes: name, uni_a, uni_z
183
        Verify if the attributes uni_a and uni_z are valid.
184
185
        Raises:
186
            ValueError: message with error detail.
187
188
        """
189
        for attribute in self.required_attributes:
190
191
            if attribute not in kwargs:
192
                raise ValueError(f'{attribute} is required.')
193
194
            if 'uni' in attribute:
195
                uni = kwargs.get(attribute)
196
                if not isinstance(uni, UNI):
197
                    raise ValueError(f'{attribute} is an invalid UNI.')
198
199
                if not uni.is_valid():
200
                    tag = uni.user_tag.value
201
                    message = f'VLAN tag {tag} is not available in {attribute}'
202
                    raise ValueError(message)
203
204
    def __eq__(self, other):
205
        """Override the default implementation."""
206
        if not isinstance(other, EVC):
207
            return False
208
209
        attrs_to_compare = ['name', 'uni_a', 'uni_z', 'owner', 'bandwidth']
210
        for attribute in attrs_to_compare:
211
            if getattr(other, attribute) != getattr(self, attribute):
212
                return False
213
        return True
214
215
    def shares_uni(self, other):
216
        """Check if two EVCs share an UNI."""
217
        if other.uni_a in (self.uni_a, self.uni_z) or \
218
           other.uni_z in (self.uni_a, self.uni_z):
219
            return True
220
        return False
221
222
    def as_dict(self):
223
        """Return a dictionary representing an EVC object."""
224
        evc_dict = {"id": self.id, "name": self.name,
225
                    "uni_a": self.uni_a.as_dict(),
226
                    "uni_z": self.uni_z.as_dict()}
227
228
        time_fmt = "%Y-%m-%dT%H:%M:%S"
229
230
        evc_dict["start_date"] = self.start_date
231
        if isinstance(self.start_date, datetime):
232
            evc_dict["start_date"] = self.start_date.strftime(time_fmt)
233
234
        evc_dict["end_date"] = self.end_date
235
        if isinstance(self.end_date, datetime):
236
            evc_dict["end_date"] = self.end_date.strftime(time_fmt)
237
238
        evc_dict['queue_id'] = self.queue_id
239
        evc_dict['bandwidth'] = self.bandwidth
240
        evc_dict['primary_links'] = self.primary_links.as_dict()
241
        evc_dict['backup_links'] = self.backup_links.as_dict()
242
        evc_dict['current_path'] = self.current_path.as_dict()
243
        evc_dict['primary_path'] = self.primary_path.as_dict()
244
        evc_dict['backup_path'] = self.backup_path.as_dict()
245
        evc_dict['dynamic_backup_path'] = self.dynamic_backup_path
246
        evc_dict['metadata'] = self.metadata
247
248
        # if self._requested:
249
        #     request_dict = self._requested.copy()
250
        #     request_dict['uni_a'] = request_dict['uni_a'].as_dict()
251
        #     request_dict['uni_z'] = request_dict['uni_z'].as_dict()
252
        #     request_dict['circuit_scheduler'] = self.circuit_scheduler
253
        #     evc_dict['_requested'] = request_dict
254
255
        evc_dict["request_time"] = self.request_time
256
        if isinstance(self.request_time, datetime):
257
            evc_dict["request_time"] = self.request_time.strftime(time_fmt)
258
259
        time = self.creation_time.strftime(time_fmt)
260
        evc_dict['creation_time'] = time
261
262
        evc_dict['owner'] = self.owner
263
        evc_dict['circuit_scheduler'] = [sc.as_dict()
264
                                         for sc in self.circuit_scheduler]
265
266
        evc_dict['active'] = self.is_active()
267
        evc_dict['enabled'] = self.is_enabled()
268
        evc_dict['archived'] = self.archived
269
        evc_dict['priority'] = self.priority
270
271
        return evc_dict
272
273
    @property
274
    def id(self):  # pylint: disable=invalid-name
275
        """Return this EVC's ID."""
276
        return self._id
277
278
    def archive(self):
279
        """Archive this EVC on deletion."""
280
        self.archived = True
281
282
283
# pylint: disable=fixme, too-many-public-methods
284
class EVCDeploy(EVCBase):
285
    """Class to handle the deploy procedures."""
286
287
    def create(self):
288
        """Create a EVC."""
289
290
    def discover_new_paths(self):
291
        """Discover new paths to satisfy this circuit and deploy it."""
292
        return DynamicPathManager.get_best_paths(self)
293
294
    def change_path(self):
295
        """Change EVC path."""
296
297
    def reprovision(self):
298
        """Force the EVC (re-)provisioning."""
299
300
    def is_affected_by_link(self, link):
301
        """Return True if this EVC has the given link on its current path."""
302
        return link in self.current_path
303
304
    def link_affected_by_interface(self, interface):
305
        """Return True if this EVC has the given link on its current path."""
306
        return self.current_path.link_affected_by_interface(interface)
307
308
    def is_backup_path_affected_by_link(self, link):
309
        """Return True if the backup path of this EVC uses the given link."""
310
        return link in self.backup_path
311
312
    # pylint: disable=invalid-name
313
    def is_primary_path_affected_by_link(self, link):
314
        """Return True if the primary path of this EVC uses the given link."""
315
        return link in self.primary_path
316
317
    def is_using_primary_path(self):
318
        """Verify if the current deployed path is self.primary_path."""
319
        return self.primary_path and (self.current_path == self.primary_path)
320
321
    def is_using_backup_path(self):
322
        """Verify if the current deployed path is self.backup_path."""
323
        return self.backup_path and (self.current_path == self.backup_path)
324
325
    def is_using_dynamic_path(self):
326
        """Verify if the current deployed path is a dynamic path."""
327
        if self.current_path and \
328
           not self.is_using_primary_path() and \
329
           not self.is_using_backup_path() and \
330
           self.current_path.status == EntityStatus.UP:
331
            return True
332
        return False
333
334
    def deploy_to_backup_path(self):
335
        """Deploy the backup path into the datapaths of this circuit.
336
337
        If the backup_path attribute is valid and up, this method will try to
338
        deploy this backup_path.
339
340
        If everything fails and dynamic_backup_path is True, then tries to
341
        deploy a dynamic path.
342
        """
343
        # TODO: Remove flows from current (cookies)
344
        if self.is_using_backup_path():
345
            # TODO: Log to say that cannot move backup to backup
346
            return True
347
348
        success = False
349
        if self.backup_path.status is EntityStatus.UP:
350
            success = self.deploy_to_path(self.backup_path)
351
352
        if success:
353
            return True
354
355
        if self.dynamic_backup_path or \
356
           self.uni_a.interface.switch == self.uni_z.interface.switch:
357
            return self.deploy_to_path()
358
359
        return False
360
361
    def deploy_to_primary_path(self):
362
        """Deploy the primary path into the datapaths of this circuit.
363
364
        If the primary_path attribute is valid and up, this method will try to
365
        deploy this primary_path.
366
        """
367
        # TODO: Remove flows from current (cookies)
368
        if self.is_using_primary_path():
369
            # TODO: Log to say that cannot move primary to primary
370
            return True
371
372
        if self.primary_path.status is EntityStatus.UP:
373
            return self.deploy_to_path(self.primary_path)
374
        return False
375
376
    def deploy(self):
377
        """Deploy EVC to best path.
378
379
        Best path can be the primary path, if available. If not, the backup
380
        path, and, if it is also not available, a dynamic path.
381
        """
382
        if self.archived:
383
            return False
384
        self.enable()
385
        success = self.deploy_to_primary_path()
386
        if not success:
387
            success = self.deploy_to_backup_path()
388
389
        if success:
390
            emit_event(self._controller, 'deployed', evc_id=self.id)
391
        return success
392
393
    @staticmethod
394
    def get_path_status(path):
395
        """Check for the current status of a path.
396
397
        If any link in this path is down, the path is considered down.
398
        """
399
        if not path:
400
            return EntityStatus.DISABLED
401
402
        for link in path:
403
            if link.status is not EntityStatus.UP:
404
                return link.status
405
        return EntityStatus.UP
406
407
#    def discover_new_path(self):
408
#        # TODO: discover a new path to satisfy this circuit and deploy
409
410
    def remove(self):
411
        """Remove EVC path and disable it."""
412
        self.remove_current_flows()
413
        self.disable()
414
        self.sync()
415
        emit_event(self._controller, 'undeployed', evc_id=self.id)
416
417
    def remove_current_flows(self, current_path=None):
418
        """Remove all flows from current path."""
419
        switches = set()
420
421
        switches.add(self.uni_a.interface.switch)
422
        switches.add(self.uni_z.interface.switch)
423
        if not current_path:
424
            current_path = self.current_path
425
        for link in current_path:
426
            switches.add(link.endpoint_a.switch)
427
            switches.add(link.endpoint_b.switch)
428
429
        match = {'cookie': self.get_cookie(),
430
                 'cookie_mask': 18446744073709551615}
431
432
        for switch in switches:
433
            try:
434
                self._send_flow_mods(switch, [match], 'delete')
435
            except FlowModException:
436
                log.error(f'Error removing flows from switch {switch.id} for'
437
                          f'EVC {self}')
438
439
        current_path.make_vlans_available()
440
        self.current_path = Path([])
441
        self.deactivate()
442
        self.sync()
443
444
    @staticmethod
445
    def links_zipped(path=None):
446
        """Return an iterator which yields pairs of links in order."""
447
        if not path:
448
            return []
449
        return zip(path[:-1], path[1:])
450
451
    def should_deploy(self, path=None):
452
        """Verify if the circuit should be deployed."""
453
        if not path:
454
            log.debug("Path is empty.")
455
            return False
456
457
        if not self.is_enabled():
458
            log.debug(f'{self} is disabled.')
459
            return False
460
461
        if not self.is_active():
462
            log.debug(f'{self} will be deployed.')
463
            return True
464
465
        return False
466
467
    def deploy_to_path(self, path=None):
468
        """Install the flows for this circuit.
469
470
        Procedures to deploy:
471
472
        0. Remove current flows installed
473
        1. Decide if will deploy "path" or discover a new path
474
        2. Choose vlan
475
        3. Install NNI flows
476
        4. Install UNI flows
477
        5. Activate
478
        6. Update current_path
479
        7. Update links caches(primary, current, backup)
480
481
        """
482
        self.remove_current_flows()
483
        use_path = path
484
        if self.should_deploy(use_path):
485
            try:
486
                use_path.choose_vlans()
487
            except KytosNoTagAvailableError:
488
                use_path = None
489
        else:
490
            for use_path in self.discover_new_paths():
491
                if use_path is None:
492
                    continue
493
                try:
494
                    use_path.choose_vlans()
495
                    break
496
                except KytosNoTagAvailableError:
497
                    pass
498
            else:
499
                use_path = None
500
501
        try:
502
            if use_path:
503
                self._install_nni_flows(use_path)
504
                self._install_uni_flows(use_path)
505
            elif self.uni_a.interface.switch == self.uni_z.interface.switch:
506
                use_path = Path()
507
                self._install_direct_uni_flows()
508
            else:
509
                log.warn(f"{self} was not deployed. "
510
                         "No available path was found.")
511
                return False
512
        except FlowModException:
513
            log.error(f'Error deploying EVC {self} when calling flow_manager.')
514
            self.remove_current_flows(use_path)
515
            return False
516
        self.activate()
517
        self.current_path = use_path
518
        self.sync()
519
        log.info(f"{self} was deployed.")
520
        return True
521
522
    def _install_direct_uni_flows(self):
523
        """Install flows connecting two UNIs.
524
525
        This case happens when the circuit is between UNIs in the
526
        same switch.
527
        """
528
        vlan_a = self.uni_a.user_tag.value if self.uni_a.user_tag else None
529
        vlan_z = self.uni_z.user_tag.value if self.uni_z.user_tag else None
530
531
        flow_mod_az = self._prepare_flow_mod(self.uni_a.interface,
532
                                             self.uni_z.interface,
533
                                             self.queue_id)
534
        flow_mod_za = self._prepare_flow_mod(self.uni_z.interface,
535
                                             self.uni_a.interface,
536
                                             self.queue_id)
537
538
        if vlan_a and vlan_z:
539
            flow_mod_az['match']['dl_vlan'] = vlan_a
540
            flow_mod_za['match']['dl_vlan'] = vlan_z
541
            flow_mod_az['actions'].insert(0, {'action_type': 'set_vlan',
542
                                              'vlan_id': vlan_z})
543
            flow_mod_za['actions'].insert(0, {'action_type': 'set_vlan',
544
                                              'vlan_id': vlan_a})
545
        elif vlan_a:
546
            flow_mod_az['match']['dl_vlan'] = vlan_a
547
            flow_mod_az['actions'].insert(0, {'action_type': 'pop_vlan'})
548
            flow_mod_za['actions'].insert(0, {'action_type': 'set_vlan',
549
                                              'vlan_id': vlan_a})
550
        elif vlan_z:
551
            flow_mod_za['match']['dl_vlan'] = vlan_z
552
            flow_mod_za['actions'].insert(0, {'action_type': 'pop_vlan'})
553
            flow_mod_az['actions'].insert(0, {'action_type': 'set_vlan',
554
                                              'vlan_id': vlan_z})
555
        self._send_flow_mods(self.uni_a.interface.switch,
556
                             [flow_mod_az, flow_mod_za])
557
558
    def _install_nni_flows(self, path=None):
559
        """Install NNI flows."""
560
        for incoming, outcoming in self.links_zipped(path):
561
            in_vlan = incoming.get_metadata('s_vlan').value
562
            out_vlan = outcoming.get_metadata('s_vlan').value
563
564
            flows = []
565
            # Flow for one direction
566
            flows.append(self._prepare_nni_flow(incoming.endpoint_b,
567
                                                outcoming.endpoint_a,
568
                                                in_vlan, out_vlan,
569
                                                queue_id=self.queue_id))
570
571
            # Flow for the other direction
572
            flows.append(self._prepare_nni_flow(outcoming.endpoint_a,
573
                                                incoming.endpoint_b,
574
                                                out_vlan, in_vlan,
575
                                                queue_id=self.queue_id))
576
            self._send_flow_mods(incoming.endpoint_b.switch, flows)
577
578
    def _install_uni_flows(self, path=None):
579
        """Install UNI flows."""
580
        if not path:
581
            log.info('install uni flows without path.')
582
            return
583
584
        # Determine VLANs
585
        in_vlan_a = self.uni_a.user_tag.value if self.uni_a.user_tag else None
586
        out_vlan_a = path[0].get_metadata('s_vlan').value
587
588
        in_vlan_z = self.uni_z.user_tag.value if self.uni_z.user_tag else None
589
        out_vlan_z = path[-1].get_metadata('s_vlan').value
590
591
        # Flows for the first UNI
592
        flows_a = []
593
594
        # Flow for one direction, pushing the service tag
595
        push_flow = self._prepare_push_flow(self.uni_a.interface,
596
                                            path[0].endpoint_a,
597
                                            in_vlan_a, out_vlan_a,
598
                                            queue_id=self.queue_id)
599
        flows_a.append(push_flow)
600
601
        # Flow for the other direction, popping the service tag
602
        pop_flow = self._prepare_pop_flow(path[0].endpoint_a,
603
                                          self.uni_a.interface,
604
                                          in_vlan_a, out_vlan_a,
605
                                          queue_id=self.queue_id)
606
        flows_a.append(pop_flow)
607
608
        self._send_flow_mods(self.uni_a.interface.switch, flows_a)
609
610
        # Flows for the second UNI
611
        flows_z = []
612
613
        # Flow for one direction, pushing the service tag
614
        push_flow = self._prepare_push_flow(self.uni_z.interface,
615
                                            path[-1].endpoint_b,
616
                                            in_vlan_z, out_vlan_z,
617
                                            queue_id=self.queue_id)
618
        flows_z.append(push_flow)
619
620
        # Flow for the other direction, popping the service tag
621
        pop_flow = self._prepare_pop_flow(path[-1].endpoint_b,
622
                                          self.uni_z.interface,
623
                                          in_vlan_z, out_vlan_z,
624
                                          queue_id=self.queue_id)
625
        flows_z.append(pop_flow)
626
627
        self._send_flow_mods(self.uni_z.interface.switch, flows_z)
628
629
    @staticmethod
630
    def _send_flow_mods(switch, flow_mods, command='flows'):
631
        """Send a flow_mod list to a specific switch.
632
633
        Args:
634
            switch(Switch): The target of flows.
635
            flow_mods(dict): Python dictionary with flow_mods.
636
            command(str): By default is 'flows'. To remove a flow is 'remove'.
637
638
        """
639
        endpoint = f'{settings.MANAGER_URL}/{command}/{switch.id}'
640
641
        data = {"flows": flow_mods}
642
        response = requests.post(endpoint, json=data)
643
        if response.status_code >= 400:
644
            raise FlowModException
645
646
    def get_cookie(self):
647
        """Return the cookie integer from evc id."""
648
        return int(self.id, 16) + (settings.COOKIE_PREFIX << 56)
649
650
    def _prepare_flow_mod(self, in_interface, out_interface, queue_id=None):
651
        """Prepare a common flow mod."""
652
        default_actions = [{"action_type": "output",
653
                            "port": out_interface.port_number}]
654
        if queue_id:
655
            default_actions.append(
656
                {"action_type": "set_queue", "queue_id": queue_id}
657
            )
658
659
        flow_mod = {"match": {"in_port": in_interface.port_number},
660
                    "cookie": self.get_cookie(),
661
                    "actions": default_actions}
662
        if self.priority > -1:
663
            flow_mod['priority'] = self.priority
664
665
        return flow_mod
666
667
    def _prepare_nni_flow(self, *args, queue_id=None):
668
        """Create NNI flows."""
669
        in_interface, out_interface, in_vlan, out_vlan = args
670
        flow_mod = self._prepare_flow_mod(in_interface, out_interface,
671
                                          queue_id)
672
        flow_mod['match']['dl_vlan'] = in_vlan
673
674
        new_action = {"action_type": "set_vlan",
675
                      "vlan_id": out_vlan}
676
        flow_mod["actions"].insert(0, new_action)
677
678
        return flow_mod
679
680 View Code Duplication
    def _prepare_push_flow(self, *args, queue_id=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
681
        """Prepare push flow.
682
683
        Arguments:
684
            in_interface(str): Interface input.
685
            out_interface(str): Interface output.
686
            in_vlan(str): Vlan input.
687
            out_vlan(str): Vlan output.
688
689
        Return:
690
            dict: An python dictionary representing a FlowMod
691
692
        """
693
        # assign all arguments
694
        in_interface, out_interface, in_vlan, out_vlan = args
695
696
        flow_mod = self._prepare_flow_mod(in_interface, out_interface,
697
                                          queue_id)
698
699
        # the service tag must be always pushed
700
        new_action = {"action_type": "set_vlan", "vlan_id": out_vlan}
701
        flow_mod["actions"].insert(0, new_action)
702
703
        new_action = {"action_type": "push_vlan", "tag_type": "s"}
704
        flow_mod["actions"].insert(0, new_action)
705
706
        if in_vlan:
707
            # if in_vlan is set, it must be included in the match
708
            flow_mod['match']['dl_vlan'] = in_vlan
709
            new_action = {"action_type": "pop_vlan"}
710
            flow_mod["actions"].insert(0, new_action)
711
        return flow_mod
712
713 View Code Duplication
    def _prepare_pop_flow(self, in_interface, out_interface, in_vlan,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
714
                          out_vlan, queue_id=None):
715
        # pylint: disable=too-many-arguments
716
        """Prepare pop flow."""
717
        flow_mod = self._prepare_flow_mod(in_interface, out_interface,
718
                                          queue_id)
719
        flow_mod['match']['dl_vlan'] = out_vlan
720
        if in_vlan:
721
            new_action = {'action_type': 'set_vlan', 'vlan_id': in_vlan}
722
            flow_mod['actions'].insert(0, new_action)
723
            new_action = {'action_type': 'push_vlan', 'tag_type': 'c'}
724
            flow_mod['actions'].insert(0, new_action)
725
        new_action = {"action_type": "pop_vlan"}
726
        flow_mod["actions"].insert(0, new_action)
727
        return flow_mod
728
729
    @staticmethod
730
    def run_sdntrace(uni):
731
        """Run SDN trace on control plane starting from EVC UNIs."""
732
        endpoint = f'{settings.SDN_TRACE_CP_URL}/trace'
733
        data_uni = {
734
            'trace': {
735
                'switch': {
736
                    'dpid': uni.interface.switch.dpid,
737
                    'in_port': uni.interface.port_number
738
                }
739
            }
740
        }
741
        if uni.user_tag:
742
            data_uni['trace']['eth'] = {
743
                'dl_type': 0x8100,
744
                'dl_vlan': uni.user_tag.value
745
            }
746
        return requests.put(endpoint, json=data_uni)
747
748
    def check_traces(self):
749
        """Check if current_path is deployed comparing with SDN traces."""
750
        trace_a = self.run_sdntrace(self.uni_a).json()['result']
751
        if len(trace_a) != len(self.current_path) + 1:
752
            return False
753
        trace_z = self.run_sdntrace(self.uni_z).json()['result']
754
        if len(trace_z) != len(self.current_path) + 1:
755
            return False
756
757
        for link, trace1, trace2 in zip(self.current_path,
758
                                        trace_a[1:],
759
                                        trace_z[:0:-1]):
760
            if compare_endpoint_trace(
761
               link.endpoint_a,
762
               glom(link.metadata, 's_vlan.value'), trace2) is False:
763
                return False
764
            if compare_endpoint_trace(
765
               link.endpoint_b,
766
               glom(link.metadata, 's_vlan.value'), trace1) is False:
767
                return False
768
769
        return True
770
771
772
class LinkProtection(EVCDeploy):
773
    """Class to handle link protection."""
774
775
    def is_affected_by_link(self, link=None):
776
        """Verify if the current path is affected by link down event."""
777
        return self.current_path.is_affected_by_link(link)
778
779
    def is_using_primary_path(self):
780
        """Verify if the current deployed path is self.primary_path."""
781
        return self.current_path == self.primary_path
782
783
    def is_using_backup_path(self):
784
        """Verify if the current deployed path is self.backup_path."""
785
        return self.current_path == self.backup_path
786
787
    def is_using_dynamic_path(self):
788
        """Verify if the current deployed path is dynamic."""
789
        if self.current_path and \
790
           not self.is_using_primary_path() and \
791
           not self.is_using_backup_path() and \
792
           self.current_path.status is EntityStatus.UP:
793
            return True
794
        return False
795
796
    def deploy_to(self, path_name=None, path=None):
797
        """Create a deploy to path."""
798
        if self.current_path == path:
799
            log.debug(f'{path_name} is equal to current_path.')
800
            return True
801
802
        if path.status is EntityStatus.UP:
803
            return self.deploy_to_path(path)
804
805
        return False
806
807
    def handle_link_up(self, link):
808
        """Handle circuit when link down.
809
810
        Args:
811
            link(Link): Link affected by link.down event.
812
813
        """
814
        if self.is_using_primary_path():
815
            return True
816
817
        success = False
818
        if self.primary_path.is_affected_by_link(link):
819
            success = self.deploy_to_primary_path()
820
821
        if success:
822
            return True
823
824
        # We tried to deploy(primary_path) without success.
825
        # And in this case is up by some how. Nothing to do.
826
        if self.is_using_backup_path() or self.is_using_dynamic_path():
827
            return True
828
829
        # In this case, probably the circuit is not being used and
830
        # we can move to backup
831
        if self.backup_path.is_affected_by_link(link):
832
            success = self.deploy_to_backup_path()
833
834
        if success:
835
            return True
836
837
        # In this case, the circuit is not being used and we should
838
        # try a dynamic path
839
        if self.dynamic_backup_path:
840
            return self.deploy_to_path()
841
842
        return True
843
844
    def handle_link_down(self):
845
        """Handle circuit when link down.
846
847
        Returns:
848
            bool: True if the re-deploy was successly otherwise False.
849
850
        """
851
        success = False
852
        if self.is_using_primary_path():
853
            success = self.deploy_to_backup_path()
854
        elif self.is_using_backup_path():
855
            success = self.deploy_to_primary_path()
856
857
        if not success and self.dynamic_backup_path:
858
            success = self.deploy_to_path()
859
860
        if success:
861
            log.debug(f"{self} deployed after link down.")
862
        else:
863
            self.deactivate()
864
            self.current_path = Path([])
865
            self.sync()
866
            log.debug(f'Failed to re-deploy {self} after link down.')
867
868
        return success
869
870
871
class EVC(LinkProtection):
872
    """Class that represents a E-Line Virtual Connection."""
873