Passed
Pull Request — master (#28)
by
unknown
02:59
created

TestMain.test_handle_stats_reply_0x04()   A

Complexity

Conditions 1

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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