def setUp(self):
     HttpTestBase.setUp(self)
     self.client_manager = TCPClientManager(self.dc.get_connection())
Ejemplo n.º 2
0
 def __init__(self, conn):
     APIBase.__init__(self, conn)
     # TODO: determine best way to expose additional options
     self._tcp_client_manager = TCPClientManager(self._conn, secure=True)
Ejemplo n.º 3
0
class MonitorAPI(APIBase):
    """Provide access to Device Cloud Monitor API for receiving push notifications

    The Monitor API in Device Cloud allows for the creation and destruction of
    multiple "monitors."  Each monitor is registered against one or more "topics"
    which describe the data in which it is interested.

    There are, in turn, two main ways to receive data matching the topics for a
    given monitor:

    1. Stream: Device Cloud supports a protocol over TCP (optionally with SSL) over which
       the batches of events will be sent when they are received.
    2. HTTP: When batches of events are received, a configured web
       service endpoint will received a POST request with the new data.

    Currently, this library supports setting up both types of monitors, but there
    is no special support provided for parsing HTTP postback requests.

    More information on the format for topic strings can be found in the `device
    cloud documentation for monitors <http://goo.gl/6UiOCG>`_.

    Here's a quick example showing a typical pattern used for creating a push monitor
    and associated listener that triggers a callback.  Deletion of existing monitors
    matching the same topics is not necessary but sometimes done in order to ensure
    that changes to the monitor configuration in code always make it to the monitor
    configuration in Device Cloud::

        def monitor_callback(json_data):
            print(json_data)
            return True  # message received

        # Listen for DataPoint updates
        topics = ['DataPoint[U]']
        monitor = dc.monitor.get_monitor(topics)
        if monitor:
            monitor.delete()
        monitor = dc.monitor.create_tcp_monitor(topics)
        monitor.add_listener(monitor_callback)

        # later...
        dc.monitor.stop_listeners()

    When updates to any DataPoint in Device Cloud occurs, the callback will be called
    with a data structure like this one::

        {'Document': {'Msg': {'DataPoint': {'cstId': 7603,
                                            'data': 0.411700824929,
                                            'description': '',
                                            'id': '684572e0-12c4-11e5-8507-fa163ed4cf14',
                                            'quality': 0,
                                            'serverTimestamp': 1434307047694,
                                            'streamId': 'test',
                                            'streamUnits': '',
                                            'timestamp': 1434307047694},
                                'group': '*',
                                'operation': 'INSERTION',
                                'timestamp': '2015-06-14T18:37:27.815Z',
                                'topic': '7603/DataPoint/test'}}}
    """
    def __init__(self, conn):
        APIBase.__init__(self, conn)
        # TODO: determine best way to expose additional options
        self._tcp_client_manager = TCPClientManager(self._conn, secure=True)

    def create_tcp_monitor(self,
                           topics,
                           batch_size=1,
                           batch_duration=0,
                           compression='gzip',
                           format_type='json'):
        """Creates a TCP Monitor instance in Device Cloud for a given list of topics

        :param topics: a string list of topics (e.g. ['DeviceCore[U]',
                  'FileDataCore']).
        :param batch_size: How many Msgs received before sending data.
        :param batch_duration: How long to wait before sending batch if it
            does not exceed batch_size.
        :param compression: Compression value (i.e. 'gzip').
        :param format_type: What format server should send data in (i.e. 'xml' or 'json').

        Returns an object of the created Monitor
        """

        monitor_xml = """\
        <Monitor>
            <monTopic>{topics}</monTopic>
            <monBatchSize>{batch_size}</monBatchSize>
            <monFormatType>{format_type}</monFormatType>
            <monTransportType>tcp</monTransportType>
            <monCompression>{compression}</monCompression>
        </Monitor>
        """.format(
            topics=','.join(topics),
            batch_size=batch_size,
            batch_duration=batch_duration,
            format_type=format_type,
            compression=compression,
        )
        monitor_xml = textwrap.dedent(monitor_xml)

        response = self._conn.post("/ws/Monitor", monitor_xml)
        location = ET.fromstring(response.text).find('.//location').text
        monitor_id = int(location.split('/')[-1])
        return TCPDeviceCloudMonitor(self._conn, monitor_id,
                                     self._tcp_client_manager)

    def create_http_monitor(self,
                            topics,
                            transport_url,
                            transport_token=None,
                            transport_method='PUT',
                            connect_timeout=0,
                            response_timeout=0,
                            batch_size=1,
                            batch_duration=0,
                            compression='none',
                            format_type='json'):
        """Creates a HTTP Monitor instance in Device Cloud for a given list of topics

        :param topics: a string list of topics (e.g. ['DeviceCore[U]',
                  'FileDataCore']).
        :param transport_url: URL of the customer web server.
        :param transport_token: Credentials for basic authentication in the following format: username:password
        :param transport_method: HTTP method to use for sending data: PUT or POST. The default is PUT.
        :param connect_timeout: A value of 0 means use the system default of 5000 (5 seconds).
        :param response_timeout: A value of 0 means use the system default of 5000 (5 seconds).
        :param batch_size: How many Msgs received before sending data.
        :param batch_duration: How long to wait before sending batch if it
            does not exceed batch_size.
        :param compression: Compression value (i.e. 'gzip').
        :param format_type: What format server should send data in (i.e. 'xml' or 'json').

        Returns an object of the created Monitor
        """

        monitor_xml = """\
        <Monitor>
            <monTopic>{topics}</monTopic>
            <monBatchSize>{batch_size}</monBatchSize>
            <monFormatType>{format_type}</monFormatType>
            <monTransportType>http</monTransportType>
            <monTransportUrl>{transport_url}</monTransportUrl>
            <monTransportToken>{transport_token}</monTransportToken>
            <monTransportMethod>{transport_method}</monTransportMethod>
            <monConnectTimeout>{connect_timeout}</monConnectTimeout>
            <monResponseTimeout>{response_timeout}</monResponseTimeout>
            <monCompression>{compression}</monCompression>
        </Monitor>
        """.format(
            topics=','.join(topics),
            transport_url=transport_url,
            transport_token=transport_token,
            transport_method=transport_method,
            connect_timeout=connect_timeout,
            response_timeout=response_timeout,
            batch_size=batch_size,
            batch_duration=batch_duration,
            format_type=format_type,
            compression=compression,
        )
        monitor_xml = textwrap.dedent(monitor_xml)

        response = self._conn.post("/ws/Monitor", monitor_xml)
        location = ET.fromstring(response.text).find('.//location').text
        monitor_id = int(location.split('/')[-1])
        return HTTPDeviceCloudMonitor(self._conn, monitor_id)

    def get_monitors(self, condition=None, page_size=1000):
        """Return an iterator over all monitors matching the provided condition

        Get all inactive monitors and print id::

            for mon in dc.monitor.get_monitors(MON_STATUS_ATTR == "DISABLED"):
                print(mon.get_id())

        Get all the HTTP monitors and print id::

            for mon in dc.monitor.get_monitors(MON_TRANSPORT_TYPE_ATTR == "http"):
                print(mon.get_id())

        Many other possibilities exist.  See the :mod:`devicecloud.condition` documention
        for additional details on building compound expressions.

        :param condition: An :class:`.Expression` which defines the condition
            which must be matched on the monitor that will be retrieved from
            Device Cloud. If a condition is unspecified, an iterator over
            all monitors for this account will be returned.
        :type condition: :class:`.Expression` or None
        :param int page_size: The number of results to fetch in a single page.
        :return: Generator yielding :class:`.DeviceCloudMonitor` instances matching the
            provided conditions.
        """
        req_kwargs = {}
        if condition:
            req_kwargs['condition'] = condition.compile()
        for monitor_data in self._conn.iter_json_pages("/ws/Monitor",
                                                       **req_kwargs):
            yield DeviceCloudMonitor.from_json(self._conn, monitor_data,
                                               self._tcp_client_manager)

    def get_monitor(self, topics):
        """Attempts to find a Monitor in device cloud that matches the provided topics

        :param topics: a string list of topics (e.g. ``['DeviceCore[U]', 'FileDataCore'])``)

        Returns a :class:`DeviceCloudMonitor` if found, otherwise None.
        """
        for monitor in self.get_monitors(MON_TOPIC_ATTR == ",".join(topics)):
            return monitor  # return the first one, even if there are multiple
        return None

    def stop_listeners(self):
        """Stop any listener threads that may be running and join on them"""
        self._tcp_client_manager.stop()
Ejemplo n.º 4
0
 def __init__(self, conn):
     MonitorAPI.__init__(self, conn)
     self._tcp_client_manager = TCPClientManager(self._conn, secure=False)
Ejemplo n.º 5
0
 def __init__(self, conn):
     APIBase.__init__(self, conn)
     # TODO: determine best way to expose additional options
     self._tcp_client_manager = TCPClientManager(self._conn, secure=True)
Ejemplo n.º 6
0
class MonitorAPI(APIBase):
    """Provide access to Device Cloud Monitor API for receiving push notifications

    The Monitor API in Device Cloud allows for the creation and destruction of
    multiple "monitors."  Each monitor is registered against one or more "topics"
    which describe the data in which it is interested.

    There are, in turn, two main ways to receive data matching the topics for a
    given monitor:

    1. Stream: Device Cloud supports a protocol over TCP (optionally with SSL) over which
       the batches of events will be sent when they are received.
    2. HTTP: When batches of events are received, a configured web
       service endpoint will received a POST request with the new data.

    Currently, this library supports setting up both types of monitors, but there
    is no special support provided for parsing HTTP postback requests.

    More information on the format for topic strings can be found in the `device
    cloud documentation for monitors <http://goo.gl/6UiOCG>`_.

    Here's a quick example showing a typical pattern used for creating a push monitor
    and associated listener that triggers a callback.  Deletion of existing monitors
    matching the same topics is not necessary but sometimes done in order to ensure
    that changes to the monitor configuration in code always make it to the monitor
    configuration in Device Cloud::

        def monitor_callback(json_data):
            print(json_data)
            return True  # message received

        # Listen for DataPoint updates
        topics = ['DataPoint[U]']
        monitor = dc.monitor.get_monitor(topics)
        if monitor:
            monitor.delete()
        monitor = dc.monitor.create_tcp_monitor(topics)
        monitor.add_listener(monitor_callback)

        # later...
        dc.monitor.stop_listeners()

    When updates to any DataPoint in Device Cloud occurs, the callback will be called
    with a data structure like this one::

        {'Document': {'Msg': {'DataPoint': {'cstId': 7603,
                                            'data': 0.411700824929,
                                            'description': '',
                                            'id': '684572e0-12c4-11e5-8507-fa163ed4cf14',
                                            'quality': 0,
                                            'serverTimestamp': 1434307047694,
                                            'streamId': 'test',
                                            'streamUnits': '',
                                            'timestamp': 1434307047694},
                                'group': '*',
                                'operation': 'INSERTION',
                                'timestamp': '2015-06-14T18:37:27.815Z',
                                'topic': '7603/DataPoint/test'}}}
    """

    def __init__(self, conn):
        APIBase.__init__(self, conn)
        # TODO: determine best way to expose additional options
        self._tcp_client_manager = TCPClientManager(self._conn, secure=True)

    def create_tcp_monitor(self, topics, batch_size=1, batch_duration=0,
                           compression='gzip', format_type='json'):
        """Creates a TCP Monitor instance in Device Cloud for a given list of topics

        :param topics: a string list of topics (e.g. ['DeviceCore[U]',
                  'FileDataCore']).
        :param batch_size: How many Msgs received before sending data.
        :param batch_duration: How long to wait before sending batch if it
            does not exceed batch_size.
        :param compression: Compression value (i.e. 'gzip').
        :param format_type: What format server should send data in (i.e. 'xml' or 'json').

        Returns an object of the created Monitor
        """

        monitor_xml = """\
        <Monitor>
            <monTopic>{topics}</monTopic>
            <monBatchSize>{batch_size}</monBatchSize>
            <monFormatType>{format_type}</monFormatType>
            <monTransportType>tcp</monTransportType>
            <monCompression>{compression}</monCompression>
        </Monitor>
        """.format(
            topics=','.join(topics),
            batch_size=batch_size,
            batch_duration=batch_duration,
            format_type=format_type,
            compression=compression,
        )
        monitor_xml = textwrap.dedent(monitor_xml)

        response = self._conn.post("/ws/Monitor", monitor_xml)
        location = ET.fromstring(response.text).find('.//location').text
        monitor_id = int(location.split('/')[-1])
        return TCPDeviceCloudMonitor(self._conn, monitor_id, self._tcp_client_manager)

    def create_http_monitor(self, topics, transport_url, transport_token=None, transport_method='PUT', connect_timeout=0,
                            response_timeout=0, batch_size=1, batch_duration=0, compression='none', format_type='json'):
        """Creates a HTTP Monitor instance in Device Cloud for a given list of topics

        :param topics: a string list of topics (e.g. ['DeviceCore[U]',
                  'FileDataCore']).
        :param transport_url: URL of the customer web server.
        :param transport_token: Credentials for basic authentication in the following format: username:password
        :param transport_method: HTTP method to use for sending data: PUT or POST. The default is PUT.
        :param connect_timeout: A value of 0 means use the system default of 5000 (5 seconds).
        :param response_timeout: A value of 0 means use the system default of 5000 (5 seconds).
        :param batch_size: How many Msgs received before sending data.
        :param batch_duration: How long to wait before sending batch if it
            does not exceed batch_size.
        :param compression: Compression value (i.e. 'gzip').
        :param format_type: What format server should send data in (i.e. 'xml' or 'json').

        Returns an object of the created Monitor
        """

        monitor_xml = """\
        <Monitor>
            <monTopic>{topics}</monTopic>
            <monBatchSize>{batch_size}</monBatchSize>
            <monFormatType>{format_type}</monFormatType>
            <monTransportType>http</monTransportType>
            <monTransportUrl>{transport_url}</monTransportUrl>
            <monTransportToken>{transport_token}</monTransportToken>
            <monTransportMethod>{transport_method}</monTransportMethod>
            <monConnectTimeout>{connect_timeout}</monConnectTimeout>
            <monResponseTimeout>{response_timeout}</monResponseTimeout>
            <monCompression>{compression}</monCompression>
        </Monitor>
        """.format(
            topics=','.join(topics),
            transport_url=transport_url,
            transport_token=transport_token,
            transport_method=transport_method,
            connect_timeout=connect_timeout,
            response_timeout=response_timeout,
            batch_size=batch_size,
            batch_duration=batch_duration,
            format_type=format_type,
            compression=compression,
        )
        monitor_xml = textwrap.dedent(monitor_xml)

        response = self._conn.post("/ws/Monitor", monitor_xml)
        location = ET.fromstring(response.text).find('.//location').text
        monitor_id = int(location.split('/')[-1])
        return HTTPDeviceCloudMonitor(self._conn, monitor_id)

    def get_monitors(self, condition=None, page_size=1000):
        """Return an iterator over all monitors matching the provided condition

        Get all inactive monitors and print id::

            for mon in dc.monitor.get_monitors(MON_STATUS_ATTR == "DISABLED"):
                print(mon.get_id())

        Get all the HTTP monitors and print id::

            for mon in dc.monitor.get_monitors(MON_TRANSPORT_TYPE_ATTR == "http"):
                print(mon.get_id())

        Many other possibilities exist.  See the :mod:`devicecloud.condition` documention
        for additional details on building compound expressions.

        :param condition: An :class:`.Expression` which defines the condition
            which must be matched on the monitor that will be retrieved from
            Device Cloud. If a condition is unspecified, an iterator over
            all monitors for this account will be returned.
        :type condition: :class:`.Expression` or None
        :param int page_size: The number of results to fetch in a single page.
        :return: Generator yielding :class:`.DeviceCloudMonitor` instances matching the
            provided conditions.
        """
        req_kwargs = {}
        if condition:
            req_kwargs['condition'] = condition.compile()
        for monitor_data in self._conn.iter_json_pages("/ws/Monitor", **req_kwargs):
            yield DeviceCloudMonitor.from_json(self._conn, monitor_data, self._tcp_client_manager)

    def get_monitor(self, topics):
        """Attempts to find a Monitor in device cloud that matches the provided topics

        :param topics: a string list of topics (e.g. ``['DeviceCore[U]', 'FileDataCore'])``)

        Returns a :class:`DeviceCloudMonitor` if found, otherwise None.
        """
        for monitor in self.get_monitors(MON_TOPIC_ATTR == ",".join(topics)):
            return monitor  # return the first one, even if there are multiple
        return None

    def stop_listeners(self):
        """Stop any listener threads that may be running and join on them"""
        self._tcp_client_manager.stop()