Passed
Pull Request — master (#28)
by
unknown
06:51
created

TestMain.test_handle_stats_received_fail()   A

Complexity

Conditions 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 12
rs 10
c 0
b 0
f 0
cc 1
nop 2
1
"""Module to test the main napp file."""
2
import json
3
from unittest import TestCase
4
from unittest.mock import MagicMock, patch
5
from kytos.lib.helpers import (
6
    get_controller_mock,
7
    get_test_client,
8
    get_kytos_event_mock,
9
    get_switch_mock,
10
)
11
from napps.amlight.flow_stats.main import GenericFlow, Main
12
from napps.kytos.of_core.v0x01.flow import Action as Action10
13
from napps.kytos.of_core.v0x04.flow import Action as Action40
14
from napps.kytos.of_core.v0x04.match_fields import (
15
    MatchDLVLAN,
16
    MatchFieldFactory,
17
)
18
from pyof.foundation.basic_types import UBInt32
19
from pyof.v0x04.common.flow_instructions import InstructionType
20
21
22
# pylint: disable=too-many-public-methods, too-many-lines
23
class TestMain(TestCase):
24
    """Test the Main class."""
25
26
    def setUp(self):
27
        """Execute steps before each tests.
28
29
        Set the server_name_url_url from amlight/flow_stats
30
        """
31
        self.server_name_url = "http://localhost:8181/api/amlight/flow_stats"
32
        self.napp = Main(get_controller_mock())
33
34
    @staticmethod
35
    def get_napp_urls(napp):
36
        """Return the amlight/flow_stats urls.
37
38
        The urls will be like:
39
40
        urls = [
41
            (options, methods, url)
42
        ]
43
44
        """
45
        controller = napp.controller
46
        controller.api_server.register_napp_endpoints(napp)
47
48
        urls = []
49
        for rule in controller.api_server.app.url_map.iter_rules():
50
            options = {}
51
            for arg in rule.arguments:
52
                options[arg] = f"[{0}]".format(arg)
53
54
            if f"{napp.username}/{napp.name}" in str(rule):
55
                urls.append((options, rule.methods, f"{str(rule)}"))
56
57
        return urls
58
59
    def test_verify_api_urls(self):
60
        """Verify all APIs registered."""
61
62
        expected_urls = [
63
            (
64
                {"dpid": "[dpid]"},
65
                {"OPTIONS", "HEAD", "GET"},
66
                "/api/amlight/flow_stats/flow/match/ ",
67
            ),
68
            (
69
                {"dpid": "[dpid]"},
70
                {"OPTIONS", "HEAD", "GET"},
71
                "/api/amlight/flow_stats/flow/stats/",
72
            ),
73
            (
74
                {"flow_id": "[flow_id]"},
75
                {"OPTIONS", "HEAD", "GET"},
76
                "/api/amlight/flow_stats/packet_count/",
77
            ),
78
            (
79
                {"flow_id": "[flow_id]"},
80
                {"OPTIONS", "HEAD", "GET"},
81
                "/api/amlight/flow_stats/bytes_count/",
82
            ),
83
            (
84
                {"dpid": "[dpid]"},
85
                {"OPTIONS", "HEAD", "GET"},
86
                "/api/amlight/flow_stats/packet_count/per_flow/",
87
            ),
88
            (
89
                {"dpid": "[dpid]"},
90
                {"OPTIONS", "HEAD", "GET"},
91
                "/api/amlight/flow_stats/packet_count/sum/",
92
            ),
93
            (
94
                {"dpid": "[dpid]"},
95
                {"OPTIONS", "HEAD", "GET"},
96
                "/api/amlight/flow_stats/bytes_count/per_flow/",
97
            ),
98
            (
99
                {"dpid": "[dpid]"},
100
                {"OPTIONS", "HEAD", "GET"},
101
                "/api/amlight/flow_stats/bytes_count/sum/",
102
            ),
103
        ]
104
        urls = self.get_napp_urls(self.napp)
105
        self.assertEqual(len(expected_urls), len(urls))
106
107
    def test_packet_count__fail(self):
108
        """Test packet_count rest call with wrong flow_id."""
109
        flow_id = "123456789"
110
        rest_name = "packet_count"
111
        response = self._get_rest_response(rest_name, flow_id)
112
113
        self.assertEqual(response.data, b"Flow does not exist")
114
115
    def test_packet_count(self):
116
        """Test packet_count rest call."""
117
        flow_id = "1"
118
        rest_name = "packet_count"
119
        self._patch_switch_flow(flow_id)
120
        response = self._get_rest_response(rest_name, flow_id)
121
122
        json_response = json.loads(response.data)
123
        self.assertEqual(json_response["flow_id"], flow_id)
124
        self.assertEqual(json_response["packet_counter"], 40)
125
        self.assertEqual(json_response["packet_per_second"], 2.0)
126
127
    def test_bytes_count_fail(self):
128
        """Test bytes_count rest call with wrong flow_id."""
129
        flow_id = "123456789"
130
        rest_name = "bytes_count"
131
        response = self._get_rest_response(rest_name, flow_id)
132
133
        self.assertEqual(response.data, b"Flow does not exist")
134
135
    def test_bytes_count(self):
136
        """Test bytes_count rest call."""
137
        flow_id = "1"
138
        rest_name = "bytes_count"
139
        self._patch_switch_flow(flow_id)
140
        response = self._get_rest_response(rest_name, flow_id)
141
142
        json_response = json.loads(response.data)
143
        self.assertEqual(json_response["flow_id"], flow_id)
144
        self.assertEqual(json_response["bytes_counter"], 10)
145
        self.assertEqual(json_response["bits_per_second"], 4.0)
146
147 View Code Duplication
    def test_packet_count_per_flow(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
148
        """Test packet_count_per_flow rest call."""
149
        flow_id = "1"
150
        rest_name = "packet_count/per_flow"
151
        self._patch_switch_flow(flow_id)
152
153
        dpid_id = 111
154
        response = self._get_rest_response(rest_name, dpid_id)
155
156
        json_response = json.loads(response.data)
157
        self.assertEqual(json_response[0]["flow_id"], flow_id)
158
        self.assertEqual(json_response[0]["packet_counter"], 40)
159
        self.assertEqual(json_response[0]["packet_per_second"], 2.0)
160
161
    def test_packet_count_sum(self):
162
        """Test packet_count_sum rest call."""
163
        flow_id = "1"
164
        rest_name = "packet_count/sum"
165
        self._patch_switch_flow(flow_id)
166
167
        dpid_id = 111
168
        response = self._get_rest_response(rest_name, dpid_id)
169
        json_response = json.loads(response.data)
170
171
        self.assertEqual(json_response, 40)
172
173 View Code Duplication
    def test_bytes_count_per_flow(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
174
        """Test bytes_count_per_flow rest call."""
175
        flow_id = "1"
176
        rest_name = "bytes_count/per_flow"
177
        self._patch_switch_flow(flow_id)
178
179
        dpid_id = 111
180
        response = self._get_rest_response(rest_name, dpid_id)
181
182
        json_response = json.loads(response.data)
183
        self.assertEqual(json_response[0]["flow_id"], flow_id)
184
        self.assertEqual(json_response[0]["bytes_counter"], 10)
185
        self.assertEqual(json_response[0]["bits_per_second"], 4.0)
186
187
    def test_bytes_count_sum(self):
188
        """Test bytes_count_sum rest call."""
189
        flow_id = "1"
190
        rest_name = "bytes_count/sum"
191
        self._patch_switch_flow(flow_id)
192
193
        dpid_id = 111
194
        response = self._get_rest_response(rest_name, dpid_id)
195
        json_response = json.loads(response.data)
196
197
        self.assertEqual(json_response, 10)
198
199
    @patch("napps.amlight.flow_stats.main.Main.match_flows")
200
    def test_flow_match(self, mock_match_flows):
201
        """Test flow_match rest call."""
202
        flow = GenericFlow()
203
        flow.actions = [
204
            Action10.from_dict(
205
                {
206
                    "action_type": "output",
207
                    "port": "1",
208
                }
209
            ),
210
        ]
211
        flow.version = "0x04"
212
        mock_match_flows.return_value = flow
213
214
        flow_id = "1"
215
        rest_name = "flow/match"
216
        self._patch_switch_flow(flow_id)
217
218
        dpid_id = "aa:00:00:00:00:00:00:11"
219
        response = self._get_rest_response(rest_name, dpid_id)
220
        json_response = json.loads(response.data)
221
222
        self.assertEqual(response.status_code, 200)
223
        self.assertEqual(json_response["actions"][0]["action_type"], "output")
224
        self.assertEqual(json_response["actions"][0]["port"], "1")
225
        self.assertEqual(json_response["version"], "0x04")
226
227
    @patch("napps.amlight.flow_stats.main.Main.match_flows")
228
    def test_flow_match_fail(self, mock_match_flows):
229
        """Test flow_match rest call."""
230
        mock_match_flows.return_value = None
231
232
        flow_id = "1"
233
        rest_name = "flow/match"
234
        self._patch_switch_flow(flow_id)
235
236
        dpid_id = "aa:00:00:00:00:00:00:11"
237
        response = self._get_rest_response(rest_name, dpid_id)
238
239
        self.assertEqual(response.status_code, 404)
240
241
    @patch("napps.amlight.flow_stats.main.Main.match_flows")
242
    def test_flow_stats(self, mock_match_flows):
243
        """Test flow_match rest call."""
244
        flow = GenericFlow()
245
        flow.actions = [
246
            Action10.from_dict(
247
                {
248
                    "action_type": "output",
249
                    "port": "1",
250
                }
251
            ),
252
        ]
253
        flow.version = "0x04"
254
        mock_match_flows.return_value = [flow]
255
256
        flow_id = "1"
257
        rest_name = "flow/stats"
258
        self._patch_switch_flow(flow_id)
259
260
        dpid_id = "aa:00:00:00:00:00:00:11"
261
        response = self._get_rest_response(rest_name, dpid_id)
262
        json_response = json.loads(response.data)
263
264
        self.assertEqual(response.status_code, 200)
265
        print(json_response)
266
        self.assertEqual(json_response[0]["actions"][0]["action_type"],
267
                         "output")
268
        self.assertEqual(json_response[0]["actions"][0]["port"], "1")
269
        self.assertEqual(json_response[0]["version"], "0x04")
270
271
    def _patch_switch_flow(self, flow_id):
272
        """Helper method to patch controller to return switch/flow data."""
273
        # patching the flow_stats object in the switch
274
        flow = self._get_mocked_flow_stats()
275
        flow.id = flow_id
276
        switch = MagicMock()
277
        switch.generic_flows = [flow]
278
        self.napp.controller.switches = {"1": switch}
279
        self.napp.controller.get_switch_by_dpid = MagicMock()
280
        self.napp.controller.get_switch_by_dpid.return_value = switch
281
282
    def _get_rest_response(self, rest_name, url_id):
283
        """Helper method to call a rest endpoint."""
284
        # call rest
285
        api = get_test_client(get_controller_mock(), self.napp)
286
        url = f"{self.server_name_url}/{rest_name}/{url_id}"
287
        response = api.get(url, content_type="application/json")
288
289
        return response
290
291
    # pylint: disable=no-self-use
292
    def _get_mocked_flow_stats(self):
293
        """Helper method to create a mock flow_stats object."""
294
        flow_stats = MagicMock()
295
        flow_stats.id = 123
296
        flow_stats.byte_count = 10
297
        flow_stats.duration_sec = 20
298
        flow_stats.duration_nsec = 30
299
        flow_stats.packet_count = 40
300
        return flow_stats
301
302
    def _get_mocked_multipart_replies_flows(self):
303
        """Helper method to create mock multipart replies flows"""
304
        msg = MagicMock()
305
        msg.header.message_type.name = 'ofpt_multipart_reply'
306
        replies_flows = [msg]
307
        return replies_flows
308
309
    def _get_mocked_flow_base(self):
310
        """Helper method to create a mock flow object."""
311
        flow = MagicMock()
312
        flow.id = 456
313
        flow.switch = None
314
        flow.table_id = None
315
        flow.match = None
316
        flow.priority = None
317
        flow.idle_timeout = None
318
        flow.hard_timeout = None
319
        flow.cookie = None
320
        flow.stats = self._get_mocked_flow_stats()
321
        return flow
322
323
    @patch("napps.amlight.flow_stats.main.GenericFlow.from_flow_stats")
324
    def test_handle_stats_reply(self, mock_from_flow):
325
        """Test handle_stats_reply rest call."""
326
        mock_from_flow.return_value = self._get_mocked_flow_base()
327
328
        def side_effect(event):
329
            self.assertTrue(f"{event}", "amlight/flow_stats.flows_updated")
330
            self.assertTrue(event.content["switch"], 111)
331
332
        self.napp.controller = MagicMock()
333
        self.napp.controller.buffers.app.put.side_effect = side_effect
334
335
        msg = MagicMock()
336
        msg.flags.value = 2
337
        msg.body = [self._get_mocked_flow_stats()]
338
        event_switch = MagicMock()
339
        event_switch.generic_flows = []
340
        event_switch.dpid = 111
341
        self.napp.handle_stats_reply(msg, event_switch)
342
343
        # Check if important trace dont trigger the event
344
        # It means that the CP trace is the same to the DP trace
345
        self.napp.controller.buffers.app.put.assert_called_once()
346
347
        # Check mocked flow id
348
        self.assertEqual(event_switch.generic_flows[0].id, 456)
349
350
    @patch("napps.amlight.flow_stats.main.Main.handle_stats_reply_received")
351
    def test_handle_stats_received(self, mock_handle_stats):
352
        """Test handle stats received."""
353
354
        switch_v0x04 = get_switch_mock("00:00:00:00:00:00:00:01", 0x04)
355
        replies_flows = self._get_mocked_multipart_replies_flows()
356
        name = "kytos/of_core.flow_stats.received"
357
        content = {"switch": switch_v0x04, "replies_flows": replies_flows}
358
359
        event = get_kytos_event_mock(name=name, content=content)
360
361
        self.napp.handle_stats_received(event)
362
        mock_handle_stats.assert_called_once()
363
364
    @patch("napps.amlight.flow_stats.main.Main.handle_stats_reply_received")
365
    def test_handle_stats_received_fail(self, mock_handle_stats):
366
        """Test handle stats received."""
367
368
        switch_v0x04 = get_switch_mock("00:00:00:00:00:00:00:01", 0x04)
369
        name = "kytos/of_core.flow_stats.received"
370
        content = {"switch": switch_v0x04}
371
372
        event = get_kytos_event_mock(name=name, content=content)
373
374
        self.napp.handle_stats_received(event)
375
        mock_handle_stats.assert_not_called()
376
377
378
# pylint: disable=too-many-public-methods, too-many-lines
379
class TestGenericFlow(TestCase):
380
    """Test the GenericFlow class."""
381
382
    # pylint: disable=no-member
383
    def test_from_flow_stats__x01(self):
384
        """Test from_flow_stats method 0x01 version."""
385
        flow_stats = MagicMock()
386
387
        flow_stats.actions = [
388
            Action10.from_dict(
389
                {
390
                    "action_type": "output",
391
                    "port": UBInt32(1),
392
                }
393
            ).as_of_action(),
394
        ]
395
396
        result = GenericFlow.from_flow_stats(flow_stats, version="0x01")
397
398
        self.assertEqual(result.idle_timeout, flow_stats.idle_timeout.value)
399
        self.assertEqual(result.hard_timeout, flow_stats.hard_timeout.value)
400
        self.assertEqual(result.priority, flow_stats.priority.value)
401
        self.assertEqual(result.table_id, flow_stats.table_id.value)
402
        self.assertEqual(result.duration_sec, flow_stats.duration_sec.value)
403
        self.assertEqual(result.packet_count, flow_stats.packet_count.value)
404
        self.assertEqual(result.byte_count, flow_stats.byte_count.value)
405
406
        exp_match = flow_stats.match
407
        self.assertEqual(result.match["wildcards"], exp_match.wildcards.value)
408
        self.assertEqual(result.match["in_port"], exp_match.in_port.value)
409
        self.assertEqual(result.match["eth_src"], exp_match.dl_src.value)
410
        self.assertEqual(result.match["eth_dst"], exp_match.dl_dst.value)
411
        self.assertEqual(result.match["vlan_vid"], exp_match.dl_vlan.value)
412
        self.assertEqual(result.match["vlan_pcp"], exp_match.dl_vlan_pcp.value)
413
        self.assertEqual(result.match["eth_type"], exp_match.dl_type.value)
414
        self.assertEqual(result.match["ip_tos"], exp_match.nw_tos.value)
415
        self.assertEqual(result.match["ipv4_src"], exp_match.nw_src.value)
416
        self.assertEqual(result.match["ipv4_dst"], exp_match.nw_dst.value)
417
        self.assertEqual(result.match["ip_proto"], exp_match.nw_proto.value)
418
        self.assertEqual(result.match["tcp_src"], exp_match.tp_src.value)
419
        self.assertEqual(result.match["tcp_dst"], exp_match.tp_dst.value)
420
421
    def test_from_flow_stats__x04_match(self):
422
        """Test from_flow_stats method 0x04 version with match."""
423
        flow_stats = MagicMock()
424
        flow_stats.actions = [
425
            Action10.from_dict(
426
                {
427
                    "action_type": "output",
428
                    "port": UBInt32(1),
429
                }
430
            ).as_of_action(),
431
        ]
432
433
        flow_stats.match.oxm_match_fields = [MatchDLVLAN(42).as_of_tlv()]
434
435
        result = GenericFlow.from_flow_stats(flow_stats, version="0x04")
436
437
        match_expect = MatchFieldFactory.from_of_tlv(
438
            flow_stats.match.oxm_match_fields[0]
439
        )
440
        self.assertEqual(result.match["vlan_vid"], match_expect)
441
442
    def test_from_flow_stats__x04_action(self):
443
        """Test from_flow_stats method 0x04 version with action."""
444
        flow_stats = MagicMock()
445
446
        action_dict = {
447
            "action_type": "output",
448
            "port": UBInt32(1),
449
        }
450
        instruction = MagicMock()
451
        instruction.instruction_type = InstructionType.OFPIT_APPLY_ACTIONS
452
        instruction.actions = [Action40.from_dict(action_dict).as_of_action()]
453
        flow_stats.instructions = [instruction]
454
455
        result = GenericFlow.from_flow_stats(flow_stats, version="0x04")
456
        self.assertEqual(result.actions[0].as_dict(), action_dict)
457
458
    def test_to_dict__x01(self):
459
        """Test to_dict method 0x01 version."""
460
        action = Action10.from_dict(
461
            {
462
                "action_type": "set_vlan",
463
                "vlan_id": 6,
464
            }
465
        )
466
        match = {}
467
        match["in_port"] = 11
468
469
        generic_flow = GenericFlow(
470
            version="0x01",
471
            match=match,
472
            idle_timeout=1,
473
            hard_timeout=2,
474
            duration_sec=3,
475
            packet_count=4,
476
            byte_count=5,
477
            priority=6,
478
            table_id=7,
479
            cookie=8,
480
            buffer_id=9,
481
            actions=[action],
482
        )
483
484
        result = generic_flow.to_dict()
485
486
        expected = {
487
            "version": "0x01",
488
            "idle_timeout": 1,
489
            "in_port": 11,
490
            "hard_timeout": 2,
491
            "priority": 6,
492
            "table_id": 7,
493
            "cookie": 8,
494
            "buffer_id": 9,
495
            "actions": [{"vlan_id": 6, "action_type": "set_vlan"}],
496
        }
497
498
        self.assertEqual(result, expected)
499
500
    def test_to_dict__x04(self):
501
        """Test to_dict method 0x04 version."""
502
        match = {}
503
        match["in_port"] = MagicMock()
504
        match["in_port"].value = 22
505
506
        generic_flow = GenericFlow(
507
            version="0x04",
508
            match=match,
509
        )
510
511
        result = generic_flow.to_dict()
512
        expected = {
513
            "version": "0x04",
514
            "in_port": 22,
515
            "idle_timeout": 0,
516
            "hard_timeout": 0,
517
            "priority": 0,
518
            "table_id": 255,
519
            "cookie": None,
520
            "buffer_id": None,
521
            "actions": [],
522
        }
523
524
        self.assertEqual(result, expected)
525
526
    def test_match_to_dict(self):
527
        """Test match_to_dict method for 0x04 version."""
528
        match = {}
529
        match["in_port"] = MagicMock()
530
        match["in_port"].value = 22
531
        match["vlan_vid"] = MagicMock()
532
        match["vlan_vid"].value = 123
533
534
        generic_flow = GenericFlow(
535
            version="0x04",
536
            match=match,
537
        )
538
        result = generic_flow.match_to_dict()
539
        expected = {"in_port": 22, "vlan_vid": 123}
540
541
        self.assertEqual(result, expected)
542
543
    def test_match_to_dict__empty_match(self):
544
        """Test match_to_dict method for 0x04 version, with empty matches."""
545
        generic_flow = GenericFlow(version="0x04")
546
        result = generic_flow.match_to_dict()
547
        self.assertEqual(result, {})
548
549
    def test_id__x01(self):
550
        """Test id method 0x01 version."""
551
        action = Action10.from_dict(
552
            {
553
                "action_type": "set_vlan",
554
                "vlan_id": 6,
555
            }
556
        )
557
        match = {}
558
        match["in_port"] = 11
559
560
        generic_flow = GenericFlow(
561
            version="0x01",
562
            match=match,
563
            idle_timeout=1,
564
            hard_timeout=2,
565
            priority=6,
566
            table_id=7,
567
            cookie=8,
568
            buffer_id=9,
569
            actions=[action],
570
        )
571
572
        self.assertEqual(generic_flow.id, "78d18f2cffd3eaa069afbf0995b90db9")
573
574
    def test_id__x04(self):
575
        """Test id method 0x04 version."""
576
        match = {}
577
        match["in_port"] = MagicMock()
578
        match["in_port"].value = 22
579
        match["vlan_vid"] = MagicMock()
580
        match["vlan_vid"].value = 123
581
582
        generic_flow = GenericFlow(
583
            version="0x04",
584
            match=match,
585
            idle_timeout=1,
586
            hard_timeout=2,
587
            priority=6,
588
            table_id=7,
589
            cookie=8,
590
            buffer_id=9,
591
        )
592
593
        self.assertEqual(generic_flow.id, "2d843f76b8b254fad6c6e2a114590440")
594