def test_subscribe_custom_threadpool(self):
     subs = SubscriptionsManager(mock.MagicMock())
     custom_threads = ThreadPool(processes=1)
     observer_a = subs.subscribe(channels.DeviceStateChanges(device_id='A'),
                                 provider=custom_threads)
     with mock.patch.object(custom_threads, 'apply_async') as custom:
         observer_a.next().defer()
         observer_a.next().defer()
         # prove that we used the threadpool we provided
         self.assertEqual(custom_threads.apply_async.call_count, 2)
    def test_notify_de_registrations(self):
        subs = SubscriptionsManager(mock.MagicMock())
        observer = subs.subscribe(channels.DeviceStateChanges())
        future = observer.next().defer()

        subs.notify(
            {channels.ChannelIdentifiers.de_registrations: ["device_id_test"]})

        output = future.get(timeout=2)
        self.assertEqual(output, {
            "device_id": "device_id_test",
            "channel": "de_registrations"
        })
    def test_notify_reg_updates(self):
        subs = SubscriptionsManager(mock.MagicMock())
        observer = subs.subscribe(channels.DeviceStateChanges())
        future = observer.next().defer()

        subs.notify({
            channels.ChannelIdentifiers.reg_updates: [{
                "ep":
                "test_endpoint_name",
                "original_ep":
                "test_original_ep",
                "ept":
                "test_ept",
                "q":
                "test_q",
                "resources": [{
                    "path": "test_path",
                    "if": "test_if",
                    "rt": "test_rt",
                    "ct": "test_ct",
                    "obs": "test_obs"
                }]
            }]
        })

        output = future.get(timeout=2)

        self.assertEqual(
            output, {
                "channel":
                "reg_updates",
                "device_id":
                "test_endpoint_name",
                "alias":
                "test_original_ep",
                "device_type":
                "test_ept",
                "queue_mode":
                "test_q",
                "resources": [{
                    "path": "test_path",
                    "type": "test_rt",
                    "content_type": "test_ct",
                    "observable": "test_obs"
                }]
            })
Example #4
0
    def __init__(self, params=None):
        """Setup the backend APIs with provided config."""
        super(ConnectAPI, self).__init__(params)

        self._db = {}
        self._queues = defaultdict(dict)

        self.b64decode = True
        self._notifications_thread = None
        self._notifications_lock = threading.RLock()
        self.subscribe = SubscriptionsManager(self)
Example #5
0
    def test_current_resource_value(self):
        device_id1 = 'ABCD_E'
        device_id2 = 'ABCD_F'
        resource_path = '/3/4/5'
        subs = SubscriptionsManager(mock.MagicMock())
        channel_a = channels.CurrentResourceValue(device_id=device_id1,
                                                  resource_path=resource_path)
        observer_a = subs.subscribe(channel_a)
        channel_b = channels.CurrentResourceValue(device_id=device_id2,
                                                  resource_path=resource_path)
        observer_b = subs.subscribe(channel_b)

        subs.notify({
            channels.ChannelIdentifiers.async_responses: [
                {
                    'id': channel_a.async_id
                },
                {
                    'id': 'unlikely'
                },
            ]
        })

        self.assertEqual(1, observer_a.notify_count)
        self.assertEqual(0, observer_b.notify_count)
    def test_parameters_as_lists(self):
        subs = SubscriptionsManager(mock.MagicMock())
        observer_a = subs.subscribe(
            channels.ResourceValues(device_id='a', resource_path=['a', 'b']))
        observer_b = subs.subscribe(
            channels.ResourceValues(device_id=['x', 'y'],
                                    resource_path=['a', 'b']))
        subs.notify({
            channels.ChannelIdentifiers.notifications: [
                # should trigger A
                {
                    'ep': 'a',
                    'path': 'a'
                },
                # should trigger A
                {
                    'ep': 'a',
                    'path': 'b'
                },
                # should trigger B
                {
                    'ep': 'x',
                    'path': 'b'
                },
            ]
        })

        self.assertEqual(2, observer_a.notify_count)
        self.assertEqual(1, observer_b.notify_count)
    def test_payload(self):
        # subscription to wildcard value should be triggered when receiving specific value
        device_id1 = 'ABCD_E'
        subs = SubscriptionsManager(mock.MagicMock())
        observer_a = subs.subscribe(
            channels.ResourceValues(device_id=device_id1,
                                    resource_path='/10255/0/4'))

        subs.notify({
            channels.ChannelIdentifiers.notifications: [
                {
                    "ct": "application/vnd.oma.lwm2m+tlv",
                    "ep": device_id1,
                    "max-age": 0,
                    "path": "/10255/0/4",
                    "payload": "iAsLSAAIAAAAAAAAAAA=",
                    'unexpected': 'extra',
                },
            ],
        })

        self.assertEqual(1, observer_a.notify_count)
        received_payload = observer_a.next().block(1)

        self.assertEqual(
            received_payload, {
                'ct': 'application/vnd.oma.lwm2m+tlv',
                'device_id': device_id1,
                'max-age': 0,
                'resource_path': '/10255/0/4',
                'payload': {
                    '11': {
                        '0': 0
                    }
                },
                'channel': 'notifications',
                'unexpected': 'extra',
            })
Example #8
0
    def test_subscribe(self):
        subs = SubscriptionsManager(mock.MagicMock())
        observer_a = subs.subscribe(channels.DeviceStateChanges(device_id='A'))
        observer_b = subs.subscribe(channels.DeviceStateChanges(device_id='B'))
        observer_c = subs.subscribe(channels.DeviceStateChanges())

        self.assertIsNot(observer_a, observer_b)
        self.assertIsNot(observer_b, observer_c)

        future_a = observer_a.next().defer()
        future_b = observer_b.next().defer()
        future_c = observer_c.next().defer()

        self.assertIsNot(future_a, future_b)
        self.assertIsNot(future_b, future_c)

        # a valid, but irrelevant channel
        subs.notify({
            channels.ChannelIdentifiers.async_responses: [
                dict(a=1, b=2, device_id='A')
            ]
        })
        time.sleep(0.01)
        self.assertFalse(future_a.ready())
        self.assertFalse(future_b.ready())
        self.assertFalse(future_c.ready())

        # a valid channel, relevant for DeviceState
        subs.notify({
            channels.ChannelIdentifiers.reg_updates: [
                dict(a=1, b=2, device_id='A')
            ]
        })

        result = future_a.get(timeout=2)
        self.assertDictContainsSubset(dict(a=1, b=2), result)

        self.assertFalse(future_b.ready())

        # possible race condition, so an unreliable check.
        # just because one observer (`a`) is ready, does not mean another (`c`) is.
        # self.assertTrue(future_c.ready())
        result = future_c.get()
        self.assertDictContainsSubset(dict(a=1, b=2), result)
    def __init__(self, params=None):
        """A module to access this section of the Pelion Device Management API.

        :param params: Dictionary to override configuration values
                     : autostart_notification_thread : Automatically starts a thread
                     if needed for Async APIs (e.g. get_resource_value).
                     This should be set to False when using webhooks for notifications.
        """
        super(ConnectAPI, self).__init__(params)

        self._db = {}
        self._queues = defaultdict(dict)

        self.b64decode = True
        self._notifications_thread = None
        self._notifications_lock = threading.RLock()
        self.subscribe = SubscriptionsManager(self)
Example #10
0
    def test_subscribe_conflict(self):
        subs = SubscriptionsManager(mock.MagicMock())
        observer_a = subs.subscribe(
            channels.ResourceValueCurrent(device_id='A', resource_path=5))
        observer_b = subs.subscribe(
            channels.ResourceValueCurrent(device_id='B', resource_path=5))
        channel_c = channels.ResourceValueCurrent(device_id='A',
                                                  resource_path=2)
        observer_c = subs.subscribe(channel_c)

        subs.notify({
            channels._API_CHANNELS.async_responses: [
                dict(a=1,
                     b=2,
                     device_id='A',
                     resource_path=2,
                     id=channel_c.async_id)
            ]
        })

        result = observer_c.next().defer().get(timeout=2)
        self.assertDictContainsSubset(dict(a=1, b=2), result)
        self.assertDictContainsSubset(dict(a=1, b=2), result)
    def test_wildcards(self):
        # subscription to wildcard value should be triggered when receiving specific value
        device_id1 = 'ABCDEF'
        device_id2 = 'ABC123'
        subs = SubscriptionsManager(mock.MagicMock())

        # channels
        A = subs.channels.ResourceValues(device_id=device_id1,
                                         resource_path=999)
        B = subs.channels.ResourceValues(device_id='ABCD*')
        C = subs.channels.ResourceValues(device_id='ABC*', resource_path=5)
        D = subs.channels.ResourceValues(device_id=device_id1, custom_attr='x')
        E = subs.channels.ResourceValues(device_id='*')
        F = subs.channels.ResourceValues(resource_path=[4, 5, 6, 7])

        # set names for clarity of logging
        A.name = 'A'
        B.name = 'B'
        C.name = 'C'
        D.name = 'D'
        E.name = 'E'
        F.name = 'F'

        sequence = [A, B, C, D, E, F]

        # set up the channels
        for channel in sequence:
            subs.subscribe(channel)

        # notification payloads
        notifications = [
            # device id matches ALL but doesn't match A, C, F because of resource paths, D due to custom attr
            ({
                'ep': device_id1
            }, (B, E)),
            # device id matches ALL but doesn't match A because of resource paths, D due to custom attr
            ({
                'ep': device_id1,
                'path': '5'
            }, (B, C, E, F)),
            # should work as above
            ({
                'ep': device_id1,
                'path': '5',
                'unexpected': 'extra'
            }, (B, C, E, F)),
            # only matches D due to custom attr, E due to having any device id
            ({
                'ep': device_id1,
                'custom_attr': 'x'
            }, (B, D, E)),
            # matches C and F due to resource path
            ({
                'ep': device_id2,
                'path': '5'
            }, (C, E, F)),
            # should not trigger anything
            ({
                'endpoint': device_id1,
                'custom_attr': 'x'
            }, tuple()),
        ]

        # trigger one-by-one
        for notification, expected_triggers in notifications:
            triggered = subs.notify(
                {channels.ChannelIdentifiers.notifications: [notification]})
            expected_names = [c.name for c in expected_triggers]
            actual_names = [c.name for c in triggered]
            self.assertEqual(sorted(expected_names), sorted(actual_names))

        # trigger in one go
        triggered = subs.notify({
            channels.ChannelIdentifiers.notifications:
            [n[0] for n in notifications]
        })
        all_expected_channels = [c.name for n in notifications for c in n[1]]
        actual_names = [c.name for c in triggered]
        self.assertEqual(sorted(all_expected_channels), sorted(actual_names))

        self.assertEqual(8, B.observer.notify_count)
        self.assertEqual(2, D.observer.notify_count)
Example #12
0
    def test_routing_performance(self):
        subs = SubscriptionsManager(mock.MagicMock())
        sample_id = 42
        sample = None
        # register a bunch of subscribers
        for i in range(1000):
            subs.subscribe(channels.ResourceValues(device_id=i))
            obs = subs.subscribe(
                channels.ResourceValues(device_id=i,
                                        resource_path=['/3/0/*', '/4/0/1']))
            if i == sample_id:
                sample = obs
            subs.subscribe(
                channels.ResourceValues(**{
                    'device_id': i,
                    str(i): 'abc'
                }))

        notification_count = 33
        start = timeit.default_timer()
        for i in range(notification_count):
            subs.notify({
                'notifications': [{
                    'endpoint-name': str(42),
                    'resource-path': '/3/0/1'
                }]
            })
            subs.notify({
                'notifications': [{
                    'endpoint-name': str(42),
                    'resource-path': '/3/1'
                }]
            })
            subs.notify({
                'notifications': [{
                    'endpoint-name': str(5),
                    'resource-path': '/3/1'
                }]
            })
        stop = timeit.default_timer()
        duration = stop - start
        self.assertEqual(notification_count, sample.notify_count)

        # mildly contrived, but for cross-machine consistency we need
        # to use a benchmark to compare the lookup performance with
        self.assertLess(duration, 100 * self.bench)