Test Failed
Pull Request — master (#106)
by Vinicius
05:56 queued 03:19
created

build.models.evc.EVCDeploy.get_id_from_cookie()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
"""Classes used in the main application."""
2 1
from datetime import datetime
3 1
from threading import Lock
4 1
from uuid import uuid4
5
6 1
import requests
7 1
from glom import glom
8
9 1
from kytos.core import log
10 1
from kytos.core.common import EntityStatus, GenericEntity
11 1
from kytos.core.exceptions import KytosNoTagAvailableError
12 1
from kytos.core.helpers import get_time, now
13 1
from kytos.core.interface import UNI
14 1
from napps.kytos.mef_eline import settings
15 1
from napps.kytos.mef_eline.exceptions import FlowModException, InvalidPath
16 1
from napps.kytos.mef_eline.storehouse import StoreHouse
17 1
from napps.kytos.mef_eline.utils import compare_endpoint_trace, emit_event
18 1
from .path import Path, DynamicPathManager
19
20
21 1
class EVCBase(GenericEntity):
22
    """Class to represent a circuit."""
23
24 1
    read_only_attributes = [
25
        'creation_time', 'active', 'current_path',
26
        '_id', 'archived'
27
    ]
28 1
    attributes_requiring_redeploy = [
29
        'primary_path', 'backup_path', 'dynamic_backup_path', 'queue_id',
30
        'priority'
31
    ]
32 1
    required_attributes = ['name', 'uni_a', 'uni_z']
33
34 1
    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 1
        self._validate(**kwargs)
74 1
        super().__init__()
75
76
        # required attributes
77 1
        self._id = kwargs.get('id', uuid4().hex)[:14]
78 1
        self.uni_a = kwargs.get('uni_a')
79 1
        self.uni_z = kwargs.get('uni_z')
80 1
        self.name = kwargs.get('name')
81
82
        # optional attributes
83 1
        self.start_date = get_time(kwargs.get('start_date')) or now()
84 1
        self.end_date = get_time(kwargs.get('end_date')) or None
85 1
        self.queue_id = kwargs.get('queue_id', None)
86
87 1
        self.bandwidth = kwargs.get('bandwidth', 0)
88 1
        self.primary_links = Path(kwargs.get('primary_links', []))
89 1
        self.backup_links = Path(kwargs.get('backup_links', []))
90 1
        self.current_path = Path(kwargs.get('current_path', []))
91 1
        self.primary_path = Path(kwargs.get('primary_path', []))
92 1
        self.backup_path = Path(kwargs.get('backup_path', []))
93 1
        self.dynamic_backup_path = kwargs.get('dynamic_backup_path', False)
94 1
        self.creation_time = get_time(kwargs.get('creation_time')) or now()
95 1
        self.owner = kwargs.get('owner', None)
96 1
        self.priority = kwargs.get('priority', -1)
97 1
        self.circuit_scheduler = kwargs.get('circuit_scheduler', [])
98
99 1
        self.current_links_cache = set()
100 1
        self.primary_links_cache = set()
101 1
        self.backup_links_cache = set()
102
103 1
        self.lock = Lock()
104
105 1
        self.archived = kwargs.get('archived', False)
106
107 1
        self.metadata = kwargs.get('metadata', {})
108
109 1
        self._storehouse = StoreHouse(controller)
110 1
        self._controller = controller
111
112 1
        if kwargs.get('active', False):
113 1
            self.activate()
114
        else:
115 1
            self.deactivate()
116
117 1
        if kwargs.get('enabled', False):
118 1
            self.enable()
119
        else:
120 1
            self.disable()
121
122
        # datetime of user request for a EVC (or datetime when object was
123
        # created)
124 1
        self.request_time = kwargs.get('request_time', now())
125
        # dict with the user original request (input)
126 1
        self._requested = kwargs
127
128 1
    def sync(self):
129
        """Sync this EVC in the storehouse."""
130 1
        self._storehouse.save_evc(self)
131 1
        log.info(f'EVC {self.id} was synced to the storehouse.')
132
133 1
    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 1
        enable, redeploy = (None, None)
147 1
        uni_a = kwargs.get('uni_a') or self.uni_a
148 1
        uni_z = kwargs.get('uni_z') or self.uni_z
149 1
        for attribute, value in kwargs.items():
150 1
            if attribute in self.read_only_attributes:
151 1
                raise ValueError(f'{attribute} can\'t be updated.')
152 1
            if not hasattr(self, attribute):
153
                raise ValueError(f'The attribute "{attribute}" is invalid.')
154 1
            if attribute in ('primary_path', 'backup_path'):
155 1
                try:
156 1
                    value.is_valid(uni_a.interface.switch,
157
                                   uni_z.interface.switch)
158 1
                except InvalidPath as exception:
159 1
                    raise ValueError(f'{attribute} is not a '
160
                                     f'valid path: {exception}')
161 1
        for attribute, value in kwargs.items():
162 1
            if attribute in ('enable', 'enabled'):
163 1
                if value:
164 1
                    self.enable()
165
                else:
166 1
                    self.disable()
167 1
                enable = value
168
            else:
169 1
                setattr(self, attribute, value)
170 1
                if attribute in self.attributes_requiring_redeploy:
171 1
                    redeploy = value
172 1
        self.sync()
173 1
        return enable, redeploy
174
175 1
    def __repr__(self):
176
        """Repr method."""
177 1
        return f"EVC({self._id}, {self.name})"
178
179 1
    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 1
        for attribute in self.required_attributes:
190
191 1
            if attribute not in kwargs:
192 1
                raise ValueError(f'{attribute} is required.')
193
194 1
            if 'uni' in attribute:
195 1
                uni = kwargs.get(attribute)
196 1
                if not isinstance(uni, UNI):
197
                    raise ValueError(f'{attribute} is an invalid UNI.')
198
199 1
                if not uni.is_valid():
200 1
                    tag = uni.user_tag.value
201 1
                    message = f'VLAN tag {tag} is not available in {attribute}'
202 1
                    raise ValueError(message)
203
204 1
    def __eq__(self, other):
205
        """Override the default implementation."""
206 1
        if not isinstance(other, EVC):
207
            return False
208
209 1
        attrs_to_compare = ['name', 'uni_a', 'uni_z', 'owner', 'bandwidth']
210 1
        for attribute in attrs_to_compare:
211 1
            if getattr(other, attribute) != getattr(self, attribute):
212 1
                return False
213 1
        return True
214
215 1
    def shares_uni(self, other):
216
        """Check if two EVCs share an UNI."""
217 1
        if other.uni_a in (self.uni_a, self.uni_z) or \
218
           other.uni_z in (self.uni_a, self.uni_z):
219 1
            return True
220
        return False
221
222 1
    def as_dict(self):
223
        """Return a dictionary representing an EVC object."""
224 1
        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 1
        time_fmt = "%Y-%m-%dT%H:%M:%S"
229
230 1
        evc_dict["start_date"] = self.start_date
231 1
        if isinstance(self.start_date, datetime):
232 1
            evc_dict["start_date"] = self.start_date.strftime(time_fmt)
233
234 1
        evc_dict["end_date"] = self.end_date
235 1
        if isinstance(self.end_date, datetime):
236 1
            evc_dict["end_date"] = self.end_date.strftime(time_fmt)
237
238 1
        evc_dict['queue_id'] = self.queue_id
239 1
        evc_dict['bandwidth'] = self.bandwidth
240 1
        evc_dict['primary_links'] = self.primary_links.as_dict()
241 1
        evc_dict['backup_links'] = self.backup_links.as_dict()
242 1
        evc_dict['current_path'] = self.current_path.as_dict()
243 1
        evc_dict['primary_path'] = self.primary_path.as_dict()
244 1
        evc_dict['backup_path'] = self.backup_path.as_dict()
245 1
        evc_dict['dynamic_backup_path'] = self.dynamic_backup_path
246 1
        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 1
        evc_dict["request_time"] = self.request_time
256 1
        if isinstance(self.request_time, datetime):
257 1
            evc_dict["request_time"] = self.request_time.strftime(time_fmt)
258
259 1
        time = self.creation_time.strftime(time_fmt)
260 1
        evc_dict['creation_time'] = time
261
262 1
        evc_dict['owner'] = self.owner
263 1
        evc_dict['circuit_scheduler'] = [sc.as_dict()
264
                                         for sc in self.circuit_scheduler]
265
266 1
        evc_dict['active'] = self.is_active()
267 1
        evc_dict['enabled'] = self.is_enabled()
268 1
        evc_dict['archived'] = self.archived
269 1
        evc_dict['priority'] = self.priority
270
271 1
        return evc_dict
272
273 1
    @property
274
    def id(self):  # pylint: disable=invalid-name
275
        """Return this EVC's ID."""
276 1
        return self._id
277
278 1
    def archive(self):
279
        """Archive this EVC on deletion."""
280 1
        self.archived = True
281
282
283
# pylint: disable=fixme, too-many-public-methods
284 1
class EVCDeploy(EVCBase):
285
    """Class to handle the deploy procedures."""
286
287 1
    def create(self):
288
        """Create a EVC."""
289
290 1
    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 1
    def change_path(self):
295
        """Change EVC path."""
296
297 1
    def reprovision(self):
298
        """Force the EVC (re-)provisioning."""
299
300 1
    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 1
    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 1
    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 1
    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 1
    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 1
    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 1
    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 1
    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 1
        if self.is_using_backup_path():
345
            # TODO: Log to say that cannot move backup to backup
346
            return True
347
348 1
        success = False
349 1
        if self.backup_path.status is EntityStatus.UP:
350 1
            success = self.deploy_to_path(self.backup_path)
351
352 1
        if success:
353 1
            return True
354
355 1
        if self.dynamic_backup_path or \
356
           self.uni_a.interface.switch == self.uni_z.interface.switch:
357 1
            return self.deploy_to_path()
358
359
        return False
360
361 1
    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 1
        if self.is_using_primary_path():
369
            # TODO: Log to say that cannot move primary to primary
370
            return True
371
372 1
        if self.primary_path.status is EntityStatus.UP:
373 1
            return self.deploy_to_path(self.primary_path)
374
        return False
375
376 1
    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 1
    @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 1
    def remove(self):
411
        """Remove EVC path and disable it."""
412 1
        self.remove_current_flows()
413 1
        self.disable()
414 1
        self.sync()
415 1
        emit_event(self._controller, 'undeployed', evc_id=self.id)
416
417 1
    def remove_current_flows(self, current_path=None, force=True):
418
        """Remove all flows from current path."""
419 1
        switches = set()
420
421 1
        switches.add(self.uni_a.interface.switch)
422 1
        switches.add(self.uni_z.interface.switch)
423 1
        if not current_path:
424 1
            current_path = self.current_path
425 1
        for link in current_path:
426 1
            switches.add(link.endpoint_a.switch)
427 1
            switches.add(link.endpoint_b.switch)
428
429 1
        match = {'cookie': self.get_cookie(),
430
                 'cookie_mask': 18446744073709551615}
431
432 1
        for switch in switches:
433 1
            try:
434 1
                self._send_flow_mods(switch, [match], 'delete', force=force)
435
            except FlowModException:
436
                log.error(f'Error removing flows from switch {switch.id} for'
437
                          f'EVC {self}')
438
439 1
        current_path.make_vlans_available()
440 1
        self.current_path = Path([])
441 1
        self.deactivate()
442 1
        self.sync()
443
444 1
    @staticmethod
445 1
    def links_zipped(path=None):
446
        """Return an iterator which yields pairs of links in order."""
447 1
        if not path:
448
            return []
449 1
        return zip(path[:-1], path[1:])
450
451 1
    def should_deploy(self, path=None):
452
        """Verify if the circuit should be deployed."""
453 1
        if not path:
454 1
            log.debug("Path is empty.")
455 1
            return False
456
457 1
        if not self.is_enabled():
458 1
            log.debug(f'{self} is disabled.')
459 1
            return False
460
461 1
        if not self.is_active():
462 1
            log.debug(f'{self} will be deployed.')
463 1
            return True
464
465 1
        return False
466
467 1
    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 1
        self.remove_current_flows()
483 1
        use_path = path
484 1
        if self.should_deploy(use_path):
485 1
            try:
486 1
                use_path.choose_vlans()
487
            except KytosNoTagAvailableError:
488
                use_path = None
489
        else:
490 1
            for use_path in self.discover_new_paths():
491 1
                if use_path is None:
492
                    continue
493 1
                try:
494 1
                    use_path.choose_vlans()
495 1
                    break
496
                except KytosNoTagAvailableError:
497
                    pass
498
            else:
499 1
                use_path = None
500
501 1
        try:
502 1
            if use_path:
503 1
                self._install_nni_flows(use_path)
504 1
                self._install_uni_flows(use_path)
505 1
            elif self.uni_a.interface.switch == self.uni_z.interface.switch:
506
                use_path = Path()
507
                self._install_direct_uni_flows()
508
            else:
509 1
                log.warn(f"{self} was not deployed. "
510
                         "No available path was found.")
511 1
                return False
512 1
        except FlowModException:
513 1
            log.error(f'Error deploying EVC {self} when calling flow_manager.')
514 1
            self.remove_current_flows(use_path)
515 1
            return False
516 1
        self.activate()
517 1
        self.current_path = use_path
518 1
        self.sync()
519 1
        log.info(f"{self} was deployed.")
520 1
        return True
521
522 1
    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 1
    def _install_nni_flows(self, path=None):
559
        """Install NNI flows."""
560 1
        for incoming, outcoming in self.links_zipped(path):
561 1
            in_vlan = incoming.get_metadata('s_vlan').value
562 1
            out_vlan = outcoming.get_metadata('s_vlan').value
563
564 1
            flows = []
565
            # Flow for one direction
566 1
            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 1
            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 1
            self._send_flow_mods(incoming.endpoint_b.switch, flows)
577
578 1
    def _install_uni_flows(self, path=None):
579
        """Install UNI flows."""
580 1
        if not path:
581
            log.info('install uni flows without path.')
582
            return
583
584
        # Determine VLANs
585 1
        in_vlan_a = self.uni_a.user_tag.value if self.uni_a.user_tag else None
586 1
        out_vlan_a = path[0].get_metadata('s_vlan').value
587
588 1
        in_vlan_z = self.uni_z.user_tag.value if self.uni_z.user_tag else None
589 1
        out_vlan_z = path[-1].get_metadata('s_vlan').value
590
591
        # Flows for the first UNI
592 1
        flows_a = []
593
594
        # Flow for one direction, pushing the service tag
595 1
        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 1
        flows_a.append(push_flow)
600
601
        # Flow for the other direction, popping the service tag
602 1
        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 1
        flows_a.append(pop_flow)
607
608 1
        self._send_flow_mods(self.uni_a.interface.switch, flows_a)
609
610
        # Flows for the second UNI
611 1
        flows_z = []
612
613
        # Flow for one direction, pushing the service tag
614 1
        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 1
        flows_z.append(push_flow)
619
620
        # Flow for the other direction, popping the service tag
621 1
        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 1
        flows_z.append(pop_flow)
626
627 1
        self._send_flow_mods(self.uni_z.interface.switch, flows_z)
628
629 1
    @staticmethod
630 1
    def _send_flow_mods(switch, flow_mods, command='flows', force=False):
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
            force(bool): True to send via consistency check in case of conn errors
638
639
        """
640 1
        endpoint = f'{settings.MANAGER_URL}/{command}/{switch.id}'
641
642 1
        data = {"flows": flow_mods, "force": force}
643 1
        response = requests.post(endpoint, json=data)
644 1
        if response.status_code >= 400:
645
            raise FlowModException
646
647 1
    def get_cookie(self):
648
        """Return the cookie integer from evc id."""
649 1
        return int(self.id, 16) + (settings.COOKIE_PREFIX << 56)
650
651 1
    @staticmethod
652
    def get_id_from_cookie(cookie):
653 1
        """Return the evc id given a cookie value."""
654
        evc_id = cookie - (settings.COOKIE_PREFIX << 56)
655 1
        return f"{evc_id:x}"
656
657
    def _prepare_flow_mod(self, in_interface, out_interface, queue_id=None):
658
        """Prepare a common flow mod."""
659
        default_actions = [{"action_type": "output",
660 1
                            "port": out_interface.port_number}]
661
        if queue_id:
662
            default_actions.append(
663 1
                {"action_type": "set_queue", "queue_id": queue_id}
664
            )
665
666 1
        flow_mod = {"match": {"in_port": in_interface.port_number},
667
                    "cookie": self.get_cookie(),
668 1
                    "actions": default_actions}
669
        if self.priority > -1:
670 1
            flow_mod['priority'] = self.priority
671 1
672
        return flow_mod
673 1
674
    def _prepare_nni_flow(self, *args, queue_id=None):
675 1
        """Create NNI flows."""
676
        in_interface, out_interface, in_vlan, out_vlan = args
677 1
        flow_mod = self._prepare_flow_mod(in_interface, out_interface,
678
                                          queue_id)
679 1
        flow_mod['match']['dl_vlan'] = in_vlan
680
681 1
        new_action = {"action_type": "set_vlan",
682
                      "vlan_id": out_vlan}
683
        flow_mod["actions"].insert(0, new_action)
684
685
        return flow_mod
686
687 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...
688
        """Prepare push flow.
689
690
        Arguments:
691
            in_interface(str): Interface input.
692
            out_interface(str): Interface output.
693
            in_vlan(str): Vlan input.
694
            out_vlan(str): Vlan output.
695 1
696
        Return:
697 1
            dict: An python dictionary representing a FlowMod
698
699
        """
700
        # assign all arguments
701 1
        in_interface, out_interface, in_vlan, out_vlan = args
702 1
703
        flow_mod = self._prepare_flow_mod(in_interface, out_interface,
704 1
                                          queue_id)
705 1
706
        # the service tag must be always pushed
707 1
        new_action = {"action_type": "set_vlan", "vlan_id": out_vlan}
708
        flow_mod["actions"].insert(0, new_action)
709 1
710 1
        new_action = {"action_type": "push_vlan", "tag_type": "s"}
711 1
        flow_mod["actions"].insert(0, new_action)
712 1
713
        if in_vlan:
714 1
            # if in_vlan is set, it must be included in the match
715
            flow_mod['match']['dl_vlan'] = in_vlan
716
            new_action = {"action_type": "pop_vlan"}
717
            flow_mod["actions"].insert(0, new_action)
718 1
        return flow_mod
719
720 1 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...
721 1
                          out_vlan, queue_id=None):
722 1
        # pylint: disable=too-many-arguments
723 1
        """Prepare pop flow."""
724 1
        flow_mod = self._prepare_flow_mod(in_interface, out_interface,
725 1
                                          queue_id)
726 1
        flow_mod['match']['dl_vlan'] = out_vlan
727 1
        if in_vlan:
728 1
            new_action = {'action_type': 'set_vlan', 'vlan_id': in_vlan}
729
            flow_mod['actions'].insert(0, new_action)
730 1
            new_action = {'action_type': 'push_vlan', 'tag_type': 'c'}
731
            flow_mod['actions'].insert(0, new_action)
732
        new_action = {"action_type": "pop_vlan"}
733
        flow_mod["actions"].insert(0, new_action)
734
        return flow_mod
735
736
    @staticmethod
737
    def run_sdntrace(uni):
738
        """Run SDN trace on control plane starting from EVC UNIs."""
739
        endpoint = f'{settings.SDN_TRACE_CP_URL}/trace'
740
        data_uni = {
741
            'trace': {
742
                'switch': {
743
                    'dpid': uni.interface.switch.dpid,
744
                    'in_port': uni.interface.port_number
745
                }
746
            }
747
        }
748
        if uni.user_tag:
749 1
            data_uni['trace']['eth'] = {
750
                'dl_type': 0x8100,
751
                'dl_vlan': uni.user_tag.value
752
            }
753
        return requests.put(endpoint, json=data_uni)
754
755
    def check_traces(self):
756
        """Check if current_path is deployed comparing with SDN traces."""
757
        trace_a = self.run_sdntrace(self.uni_a).json()['result']
758
        if len(trace_a) != len(self.current_path) + 1:
759
            return False
760
        trace_z = self.run_sdntrace(self.uni_z).json()['result']
761
        if len(trace_z) != len(self.current_path) + 1:
762
            return False
763
764
        for link, trace1, trace2 in zip(self.current_path,
765
                                        trace_a[1:],
766
                                        trace_z[:0:-1]):
767
            if compare_endpoint_trace(
768
               link.endpoint_a,
769
               glom(link.metadata, 's_vlan.value'), trace2) is False:
770
                return False
771
            if compare_endpoint_trace(
772
               link.endpoint_b,
773 1
               glom(link.metadata, 's_vlan.value'), trace1) is False:
774
                return False
775
776 1
        return True
777
778
779
class LinkProtection(EVCDeploy):
780 1
    """Class to handle link protection."""
781
782 1
    def is_affected_by_link(self, link=None):
783
        """Verify if the current path is affected by link down event."""
784 1
        return self.current_path.is_affected_by_link(link)
785
786 1
    def is_using_primary_path(self):
787
        """Verify if the current deployed path is self.primary_path."""
788 1
        return self.current_path == self.primary_path
789
790 1
    def is_using_backup_path(self):
791
        """Verify if the current deployed path is self.backup_path."""
792
        return self.current_path == self.backup_path
793
794
    def is_using_dynamic_path(self):
795 1
        """Verify if the current deployed path is dynamic."""
796
        if self.current_path and \
797 1
           not self.is_using_primary_path() and \
798
           not self.is_using_backup_path() and \
799 1
           self.current_path.status is EntityStatus.UP:
800 1
            return True
801 1
        return False
802
803 1
    def deploy_to(self, path_name=None, path=None):
804 1
        """Create a deploy to path."""
805
        if self.current_path == path:
806 1
            log.debug(f'{path_name} is equal to current_path.')
807
            return True
808 1
809
        if path.status is EntityStatus.UP:
810
            return self.deploy_to_path(path)
811
812
        return False
813
814
    def handle_link_up(self, link):
815 1
        """Handle circuit when link down.
816 1
817
        Args:
818 1
            link(Link): Link affected by link.down event.
819 1
820 1
        """
821
        if self.is_using_primary_path():
822 1
            return True
823 1
824
        success = False
825
        if self.primary_path.is_affected_by_link(link):
826
            success = self.deploy_to_primary_path()
827 1
828
        if success:
829
            return True
830
831
        # We tried to deploy(primary_path) without success.
832 1
        # And in this case is up by some how. Nothing to do.
833 1
        if self.is_using_backup_path() or self.is_using_dynamic_path():
834
            return True
835 1
836 1
        # In this case, probably the circuit is not being used and
837
        # we can move to backup
838
        if self.backup_path.is_affected_by_link(link):
839
            success = self.deploy_to_backup_path()
840
841
        if success:
842
            return True
843
844
        # In this case, the circuit is not being used and we should
845 1
        # try a dynamic path
846
        if self.dynamic_backup_path:
847
            return self.deploy_to_path()
848
849
        return True
850
851
    def handle_link_down(self):
852 1
        """Handle circuit when link down.
853 1
854 1
        Returns:
855 1
            bool: True if the re-deploy was successly otherwise False.
856 1
857
        """
858 1
        success = False
859 1
        if self.is_using_primary_path():
860
            success = self.deploy_to_backup_path()
861 1
        elif self.is_using_backup_path():
862 1
            success = self.deploy_to_primary_path()
863
864 1
        if not success and self.dynamic_backup_path:
865 1
            success = self.deploy_to_path()
866 1
867 1
        if success:
868
            log.debug(f"{self} deployed after link down.")
869 1
        else:
870
            self.deactivate()
871
            self.current_path = Path([])
872 1
            self.sync()
873
            log.debug(f'Failed to re-deploy {self} after link down.')
874
875
        return success
876
877
878
class EVC(LinkProtection):
879
    """Class that represents a E-Line Virtual Connection."""
880