def __init__(self, **kwargs):
        """Initializer for a generic synchronous client.

        This initializer should not be called directly.
        Instead, use one of the 'create_from_' classmethods to instantiate

        :param mqtt_pipeline: The MQTTPipeline used for the client
        :type mqtt_pipeline: :class:`azure.iot.device.iothub.pipeline.MQTTPipeline`
        :param http_pipeline: The HTTPPipeline used for the client
        :type http_pipeline: :class:`azure.iot.device.iothub.pipeline.HTTPPipeline`
        """
        # Depending on the subclass calling this __init__, there could be different arguments,
        # and the super() call could call a different class, due to the different MROs
        # in the class hierarchies of different clients. Thus, args here must be passed along as
        # **kwargs.
        super(GenericIoTHubClient, self).__init__(**kwargs)
        self._inbox_manager = InboxManager(inbox_type=SyncClientInbox)
        self._mqtt_pipeline.on_connected = CallableWeakMethod(
            self, "_on_connected")
        self._mqtt_pipeline.on_disconnected = CallableWeakMethod(
            self, "_on_disconnected")
        self._mqtt_pipeline.on_method_request_received = CallableWeakMethod(
            self._inbox_manager, "route_method_request")
        self._mqtt_pipeline.on_twin_patch_received = CallableWeakMethod(
            self._inbox_manager, "route_twin_patch")
Esempio n. 2
0
 def _execute_op(self, op):
     if isinstance(op, pipeline_ops_iothub.SetAuthProviderOperation):
         self.auth_provider = op.auth_provider
         self.auth_provider.on_sas_token_updated_handler = CallableWeakMethod(
             self, "on_sas_token_updated")
         self._send_worker_op_down(
             worker_op=pipeline_ops_iothub.SetIoTHubConnectionArgsOperation(
                 device_id=self.auth_provider.device_id,
                 module_id=getattr(self.auth_provider, "module_id", None),
                 hostname=self.auth_provider.hostname,
                 gateway_hostname=getattr(self.auth_provider,
                                          "gateway_hostname", None),
                 ca_cert=getattr(self.auth_provider, "ca_cert", None),
                 sas_token=self.auth_provider.get_current_sas_token(),
                 callback=op.callback,
             ),
             op=op,
         )
     elif isinstance(op, pipeline_ops_iothub.SetX509AuthProviderOperation):
         self.auth_provider = op.auth_provider
         self._send_worker_op_down(
             worker_op=pipeline_ops_iothub.SetIoTHubConnectionArgsOperation(
                 device_id=self.auth_provider.device_id,
                 module_id=getattr(self.auth_provider, "module_id", None),
                 hostname=self.auth_provider.hostname,
                 gateway_hostname=getattr(self.auth_provider,
                                          "gateway_hostname", None),
                 ca_cert=getattr(self.auth_provider, "ca_cert", None),
                 client_cert=self.auth_provider.get_x509_certificate(),
                 callback=op.callback,
             ),
             op=op,
         )
     else:
         self._send_op_down(op)
Esempio n. 3
0
    def _run_op(self, op):
        if isinstance(op, pipeline_ops_iothub.SetAuthProviderOperation):
            self.auth_provider = op.auth_provider
            self.auth_provider.on_sas_token_updated_handler = CallableWeakMethod(
                self, "_on_sas_token_updated")
            worker_op = op.spawn_worker_op(
                worker_op_type=pipeline_ops_iothub.
                SetIoTHubConnectionArgsOperation,
                device_id=self.auth_provider.device_id,
                module_id=self.auth_provider.module_id,
                hostname=self.auth_provider.hostname,
                gateway_hostname=getattr(self.auth_provider,
                                         "gateway_hostname", None),
                ca_cert=getattr(self.auth_provider, "ca_cert", None),
                sas_token=self.auth_provider.get_current_sas_token(),
            )
            self.send_op_down(worker_op)

        elif isinstance(op, pipeline_ops_iothub.SetX509AuthProviderOperation):
            self.auth_provider = op.auth_provider
            worker_op = op.spawn_worker_op(
                worker_op_type=pipeline_ops_iothub.
                SetIoTHubConnectionArgsOperation,
                device_id=self.auth_provider.device_id,
                module_id=self.auth_provider.module_id,
                hostname=self.auth_provider.hostname,
                gateway_hostname=getattr(self.auth_provider,
                                         "gateway_hostname", None),
                ca_cert=getattr(self.auth_provider, "ca_cert", None),
                client_cert=self.auth_provider.get_x509_certificate(),
            )
            self.send_op_down(worker_op)
        else:
            super(UseAuthProviderStage, self)._run_op(op)
    def __init__(self, mqtt_pipeline, http_pipeline):
        """Initializer for a IoTHubDeviceClient.

        This initializer should not be called directly.
        Instead, use one of the 'create_from_' classmethods to instantiate

        :param mqtt_pipeline: The pipeline used to connect to the IoTHub endpoint.
        :type mqtt_pipeline: :class:`azure.iot.device.iothub.pipeline.MQTTPipeline`
        """
        super(IoTHubDeviceClient, self).__init__(mqtt_pipeline=mqtt_pipeline,
                                                 http_pipeline=http_pipeline)
        self._mqtt_pipeline.on_c2d_message_received = CallableWeakMethod(
            self._inbox_manager, "route_c2d_message")
Esempio n. 5
0
    def __init__(self, **kwargs):
        """Initializer for a generic synchronous client.

        This initializer should not be called directly.
        Instead, use one of the 'create_from_' classmethods to instantiate

        TODO: How to document kwargs?
        Possible values: iothub_pipeline, edge_pipeline
        """
        # Depending on the subclass calling this __init__, there could be different arguments,
        # and the super() call could call a different class, due to the different MROs
        # in the class hierarchies of different clients. Thus, args here must be passed along as
        # **kwargs.
        super(GenericIoTHubClient, self).__init__(**kwargs)
        self._inbox_manager = InboxManager(inbox_type=SyncClientInbox)
        self._iothub_pipeline.on_connected = CallableWeakMethod(self, "_on_connected")
        self._iothub_pipeline.on_disconnected = CallableWeakMethod(self, "_on_disconnected")
        self._iothub_pipeline.on_method_request_received = CallableWeakMethod(
            self._inbox_manager, "route_method_request"
        )
        self._iothub_pipeline.on_twin_patch_received = CallableWeakMethod(
            self._inbox_manager, "route_twin_patch"
        )
Esempio n. 6
0
    def __init__(self, iothub_pipeline, http_pipeline):
        """Intializer for a IoTHubModuleClient.

        This initializer should not be called directly.
        Instead, use one of the 'create_from_' classmethods to instantiate

        :param iothub_pipeline: The pipeline used to connect to the IoTHub endpoint.
        :type iothub_pipeline: :class:`azure.iot.device.iothub.pipeline.IoTHubPipeline`
        :param http_pipeline: The pipeline used to connect to the IoTHub endpoint via HTTP.
        :type http_pipeline: :class:`azure.iot.device.iothub.pipeline.HTTPPipeline`
        """
        super(IoTHubModuleClient,
              self).__init__(iothub_pipeline=iothub_pipeline,
                             http_pipeline=http_pipeline)
        self._iothub_pipeline.on_input_message_received = CallableWeakMethod(
            self._inbox_manager, "route_input_message")
Esempio n. 7
0
    def __init__(self, iothub_pipeline, edge_pipeline=None):
        """Intializer for a IoTHubModuleClient.

        This initializer should not be called directly.
        Instead, use one of the 'create_from_' classmethods to instantiate

        :param iothub_pipeline: The pipeline used to connect to the IoTHub endpoint.
        :type iothub_pipeline: IoTHubPipeline
        :param edge_pipeline: (OPTIONAL) The pipeline used to connect to the Edge endpoint.
        :type edge_pipeline: EdgePipeline
        """
        super(IoTHubModuleClient, self).__init__(
            iothub_pipeline=iothub_pipeline, edge_pipeline=edge_pipeline
        )
        self._iothub_pipeline.on_input_message_received = CallableWeakMethod(
            self._inbox_manager, "route_input_message"
        )
 def _ensure_get_op(self):
     """
     Function which makes sure we have a GetTwin operation in progress.  If we've
     already sent one down and we're waiting for it to return, we don't want to send
     a new one down.  This is because layers below us (especially CoordinateRequestAndResponseStage)
     will do everything they can to ensure we get a response on the already-pending
     GetTwinOperation.
     """
     if not self.pending_get_request:
         logger.info("{}: sending twin GET to ensure freshness".format(
             self.name))
         self.pending_get_request = pipeline_ops_iothub.GetTwinOperation(
             callback=CallableWeakMethod(self, "_on_get_twin_complete"))
         self.send_op_down(self.pending_get_request)
     else:
         logger.info(
             "{}: Outstanding twin GET already exists.  Not sending anything"
             .format(self.name))
    def _run_op(self, op):
        if isinstance(op, pipeline_ops_iothub.SetAuthProviderOperation):
            self.auth_provider = op.auth_provider
            # Here we append rather than just add it to the handler value because otherwise it
            # would overwrite the handler from another pipeline that might be using the same auth provider.
            self.auth_provider.on_sas_token_updated_handler_list.append(
                CallableWeakMethod(self, "_on_sas_token_updated"))
            worker_op = op.spawn_worker_op(
                worker_op_type=pipeline_ops_iothub.
                SetIoTHubConnectionArgsOperation,
                device_id=self.auth_provider.device_id,
                module_id=self.auth_provider.module_id,
                hostname=self.auth_provider.hostname,
                gateway_hostname=getattr(self.auth_provider,
                                         "gateway_hostname", None),
                server_verification_cert=getattr(self.auth_provider,
                                                 "server_verification_cert",
                                                 None),
                sas_token=self.auth_provider.get_current_sas_token(),
            )
            self.send_op_down(worker_op)

        elif isinstance(op, pipeline_ops_iothub.SetX509AuthProviderOperation):
            self.auth_provider = op.auth_provider
            worker_op = op.spawn_worker_op(
                worker_op_type=pipeline_ops_iothub.
                SetIoTHubConnectionArgsOperation,
                device_id=self.auth_provider.device_id,
                module_id=self.auth_provider.module_id,
                hostname=self.auth_provider.hostname,
                gateway_hostname=getattr(self.auth_provider,
                                         "gateway_hostname", None),
                server_verification_cert=getattr(self.auth_provider,
                                                 "server_verification_cert",
                                                 None),
                client_cert=self.auth_provider.get_x509_certificate(),
            )
            self.send_op_down(worker_op)
        else:
            super(UseAuthProviderStage, self)._run_op(op)
    def _run_op(self, op):
        if isinstance(op, pipeline_ops_base.InitializePipelineOperation):

            # If there is a gateway hostname, use that as the hostname for connection,
            # rather than the hostname itself
            if self.pipeline_root.pipeline_configuration.gateway_hostname:
                logger.debug(
                    "Gateway Hostname Present. Setting Hostname to: {}".format(
                        self.pipeline_root.pipeline_configuration.
                        gateway_hostname))
                hostname = self.pipeline_root.pipeline_configuration.gateway_hostname
            else:
                logger.debug(
                    "Gateway Hostname not present. Setting Hostname to: {}".
                    format(self.pipeline_root.pipeline_configuration.hostname))
                hostname = self.pipeline_root.pipeline_configuration.hostname

            # Create the Transport object, set it's handlers
            logger.debug("{}({}): got connection args".format(
                self.name, op.name))
            self.transport = MQTTTransport(
                client_id=op.client_id,
                hostname=hostname,
                username=op.username,
                server_verification_cert=self.pipeline_root.
                pipeline_configuration.server_verification_cert,
                x509_cert=self.pipeline_root.pipeline_configuration.x509,
                websockets=self.pipeline_root.pipeline_configuration.
                websockets,
                cipher=self.pipeline_root.pipeline_configuration.cipher,
                proxy_options=self.pipeline_root.pipeline_configuration.
                proxy_options,
            )
            self.transport.on_mqtt_connected_handler = CallableWeakMethod(
                self, "_on_mqtt_connected")
            self.transport.on_mqtt_connection_failure_handler = CallableWeakMethod(
                self, "_on_mqtt_connection_failure")
            self.transport.on_mqtt_disconnected_handler = CallableWeakMethod(
                self, "_on_mqtt_disconnected")
            self.transport.on_mqtt_message_received_handler = CallableWeakMethod(
                self, "_on_mqtt_message_received")

            # There can only be one pending connection operation (Connect, ReauthorizeConnection, Disconnect)
            # at a time. The existing one must be completed or canceled before a new one is set.

            # Currently, this means that if, say, a connect operation is the pending op and is executed
            # but another connection op is begins by the time the CONNACK is received, the original
            # operation will be cancelled, but the CONNACK for it will still be received, and complete the
            # NEW operation. This is not desirable, but it is how things currently work.

            # We are however, checking the type, so the CONNACK from a cancelled Connect, cannot successfully
            # complete a Disconnect operation.
            self._pending_connection_op = None

            op.complete()

        elif isinstance(op, pipeline_ops_base.ConnectOperation):
            logger.info("{}({}): connecting".format(self.name, op.name))

            self._cancel_pending_connection_op()
            self._pending_connection_op = op
            self._start_connection_watchdog(op)
            # Use SasToken as password if present. If not present (e.g. using X509),
            # then no password is required because auth is handled via other means.
            if self.pipeline_root.pipeline_configuration.sastoken:
                password = str(
                    self.pipeline_root.pipeline_configuration.sastoken)
            else:
                password = None
            try:
                self.transport.connect(password=password)
            except Exception as e:
                logger.error("transport.connect raised error")
                logger.error(traceback.format_exc())
                self._cancel_connection_watchdog(op)
                self._pending_connection_op = None
                op.complete(error=e)

        elif isinstance(
                op, pipeline_ops_base.DisconnectOperation) or isinstance(
                    op, pipeline_ops_base.ReauthorizeConnectionOperation):
            logger.info("{}({}): disconnecting or reauthorizing".format(
                self.name, op.name))

            self._cancel_pending_connection_op()
            self._pending_connection_op = op
            # We don't need a watchdog on disconnect because there's no callback to wait for
            # and we respond to a watchdog timeout by calling disconnect, which is what we're
            # already doing.

            try:
                self.transport.disconnect()
            except Exception as e:
                logger.error("transport.disconnect raised error")
                logger.error(traceback.format_exc())
                self._pending_connection_op = None
                op.complete(error=e)

        elif isinstance(op, pipeline_ops_mqtt.MQTTPublishOperation):
            logger.info("{}({}): publishing on {}".format(
                self.name, op.name, op.topic))

            @pipeline_thread.invoke_on_pipeline_thread_nowait
            def on_published():
                logger.debug("{}({}): PUBACK received. completing op.".format(
                    self.name, op.name))
                op.complete()

            try:
                self.transport.publish(topic=op.topic,
                                       payload=op.payload,
                                       callback=on_published)
            except transport_exceptions.ConnectionDroppedError:
                self.send_event_up(pipeline_events_base.DisconnectedEvent())
                raise

        elif isinstance(op, pipeline_ops_mqtt.MQTTSubscribeOperation):
            logger.info("{}({}): subscribing to {}".format(
                self.name, op.name, op.topic))

            @pipeline_thread.invoke_on_pipeline_thread_nowait
            def on_subscribed():
                logger.debug("{}({}): SUBACK received. completing op.".format(
                    self.name, op.name))
                op.complete()

            try:
                self.transport.subscribe(topic=op.topic,
                                         callback=on_subscribed)
            except transport_exceptions.ConnectionDroppedError:
                self.send_event_up(pipeline_events_base.DisconnectedEvent())
                raise

        elif isinstance(op, pipeline_ops_mqtt.MQTTUnsubscribeOperation):
            logger.info("{}({}): unsubscribing from {}".format(
                self.name, op.name, op.topic))

            @pipeline_thread.invoke_on_pipeline_thread_nowait
            def on_unsubscribed():
                logger.debug(
                    "{}({}): UNSUBACK received.  completing op.".format(
                        self.name, op.name))
                op.complete()

            try:
                self.transport.unsubscribe(topic=op.topic,
                                           callback=on_unsubscribed)
            except transport_exceptions.ConnectionDroppedError:
                self.send_event_up(pipeline_events_base.DisconnectedEvent())
                raise

        else:
            # This code block should not be reached in correct program flow.
            # This will raise an error when executed.
            self.send_op_down(op)
Esempio n. 11
0
    def _execute_op(self, op):
        if isinstance(op, pipeline_ops_mqtt.SetMQTTConnectionArgsOperation):
            # pipeline_ops_mqtt.SetMQTTConnectionArgsOperation is where we create our MQTTTransport object and set
            # all of its properties.
            logger.debug("{}({}): got connection args".format(
                self.name, op.name))
            self.hostname = op.hostname
            self.username = op.username
            self.client_id = op.client_id
            self.ca_cert = op.ca_cert
            self.sas_token = op.sas_token
            self.client_cert = op.client_cert
            self.transport = MQTTTransport(
                client_id=self.client_id,
                hostname=self.hostname,
                username=self.username,
                ca_cert=self.ca_cert,
                x509_cert=self.client_cert,
                websockets=self.pipeline_root.pipeline_configuration.
                websockets,
            )
            self.transport.on_mqtt_connected_handler = CallableWeakMethod(
                self, "_on_mqtt_connected")
            self.transport.on_mqtt_connection_failure_handler = CallableWeakMethod(
                self, "_on_mqtt_connection_failure")
            self.transport.on_mqtt_disconnected_handler = CallableWeakMethod(
                self, "_on_mqtt_disconnected")
            self.transport.on_mqtt_message_received_handler = CallableWeakMethod(
                self, "_on_mqtt_message_received")

            # There can only be one pending connection operation (Connect, Reconnect, Disconnect)
            # at a time. The existing one must be completed or canceled before a new one is set.

            # Currently, this means that if, say, a connect operation is the pending op and is executed
            # but another connection op is begins by the time the CONACK is received, the original
            # operation will be cancelled, but the CONACK for it will still be received, and complete the
            # NEW operation. This is not desirable, but it is how things currently work.

            # We are however, checking the type, so the CONACK from a cancelled Connect, cannot successfully
            # complete a Disconnect operation.
            self._pending_connection_op = None

            self.pipeline_root.transport = self.transport
            self.complete_op(op)

        elif isinstance(op, pipeline_ops_base.UpdateSasTokenOperation):
            logger.debug("{}({}): saving sas token and completing".format(
                self.name, op.name))
            self.sas_token = op.sas_token
            self.complete_op(op)

        elif isinstance(op, pipeline_ops_base.ConnectOperation):
            logger.info("{}({}): connecting".format(self.name, op.name))

            self._cancel_pending_connection_op()
            self._pending_connection_op = op
            try:
                self.transport.connect(password=self.sas_token)
            except Exception as e:
                logger.error("transport.connect raised error")
                logger.error(traceback.format_exc())
                self._pending_connection_op = None
                self.complete_op(op, error=e)

        elif isinstance(op, pipeline_ops_base.ReconnectOperation):
            logger.info("{}({}): reconnecting".format(self.name, op.name))

            # We set _active_connect_op here because a reconnect is the same as a connect for "active operation" tracking purposes.
            self._cancel_pending_connection_op()
            self._pending_connection_op = op
            try:
                self.transport.reconnect(password=self.sas_token)
            except Exception as e:
                logger.error("transport.reconnect raised error")
                logger.error(traceback.format_exc())
                self._pending_connection_op = None
                self.complete_op(op, error=e)

        elif isinstance(op, pipeline_ops_base.DisconnectOperation):
            logger.info("{}({}): disconnecting".format(self.name, op.name))

            self._cancel_pending_connection_op()
            self._pending_connection_op = op
            try:
                self.transport.disconnect()
            except Exception as e:
                logger.error("transport.disconnect raised error")
                logger.error(traceback.format_exc())
                self._pending_connection_op = None
                self.complete_op(op, error=e)

        elif isinstance(op, pipeline_ops_mqtt.MQTTPublishOperation):
            logger.info("{}({}): publishing on {}".format(
                self.name, op.name, op.topic))

            @pipeline_thread.invoke_on_pipeline_thread_nowait
            def on_published():
                logger.debug("{}({}): PUBACK received. completing op.".format(
                    self.name, op.name))
                self.complete_op(op)

            self.transport.publish(topic=op.topic,
                                   payload=op.payload,
                                   callback=on_published)

        elif isinstance(op, pipeline_ops_mqtt.MQTTSubscribeOperation):
            logger.info("{}({}): subscribing to {}".format(
                self.name, op.name, op.topic))

            @pipeline_thread.invoke_on_pipeline_thread_nowait
            def on_subscribed():
                logger.debug("{}({}): SUBACK received. completing op.".format(
                    self.name, op.name))
                self.complete_op(op)

            self.transport.subscribe(topic=op.topic, callback=on_subscribed)

        elif isinstance(op, pipeline_ops_mqtt.MQTTUnsubscribeOperation):
            logger.info("{}({}): unsubscribing from {}".format(
                self.name, op.name, op.topic))

            @pipeline_thread.invoke_on_pipeline_thread_nowait
            def on_unsubscribed():
                logger.debug(
                    "{}({}): UNSUBACK received.  completing op.".format(
                        self.name, op.name))
                self.complete_op(op)

            self.transport.unsubscribe(topic=op.topic,
                                       callback=on_unsubscribed)

        else:
            self.send_op_down(op)
    def _run_op(self, op):
        if isinstance(op, pipeline_ops_mqtt.SetMQTTConnectionArgsOperation):
            # pipeline_ops_mqtt.SetMQTTConnectionArgsOperation is where we create our MQTTTransport object and set
            # all of its properties.
            logger.debug("{}({}): got connection args".format(
                self.name, op.name))
            self.sas_token = op.sas_token
            self.transport = MQTTTransport(
                client_id=op.client_id,
                hostname=op.hostname,
                username=op.username,
                server_verification_cert=op.server_verification_cert,
                x509_cert=op.client_cert,
                websockets=self.pipeline_root.pipeline_configuration.
                websockets,
                cipher=self.pipeline_root.pipeline_configuration.cipher,
                proxy_options=self.pipeline_root.pipeline_configuration.
                proxy_options,
            )
            self.transport.on_mqtt_connected_handler = CallableWeakMethod(
                self, "_on_mqtt_connected")
            self.transport.on_mqtt_connection_failure_handler = CallableWeakMethod(
                self, "_on_mqtt_connection_failure")
            self.transport.on_mqtt_disconnected_handler = CallableWeakMethod(
                self, "_on_mqtt_disconnected")
            self.transport.on_mqtt_message_received_handler = CallableWeakMethod(
                self, "_on_mqtt_message_received")

            # There can only be one pending connection operation (Connect, ReauthorizeConnection, Disconnect)
            # at a time. The existing one must be completed or canceled before a new one is set.

            # Currently, this means that if, say, a connect operation is the pending op and is executed
            # but another connection op is begins by the time the CONNACK is received, the original
            # operation will be cancelled, but the CONNACK for it will still be received, and complete the
            # NEW operation. This is not desirable, but it is how things currently work.

            # We are however, checking the type, so the CONNACK from a cancelled Connect, cannot successfully
            # complete a Disconnect operation.
            self._pending_connection_op = None

            op.complete()

        elif isinstance(op, pipeline_ops_base.UpdateSasTokenOperation):
            logger.debug("{}({}): saving sas token and completing".format(
                self.name, op.name))
            self.sas_token = op.sas_token
            op.complete()

        elif isinstance(op, pipeline_ops_base.ConnectOperation):
            logger.info("{}({}): connecting".format(self.name, op.name))

            self._cancel_pending_connection_op()
            self._pending_connection_op = op
            self._start_connection_watchdog(op)
            try:
                self.transport.connect(password=self.sas_token)
            except Exception as e:
                logger.error("transport.connect raised error")
                logger.error(traceback.format_exc())
                self._cancel_connection_watchdog(op)
                self._pending_connection_op = None
                op.complete(error=e)

        elif isinstance(op, pipeline_ops_base.ReauthorizeConnectionOperation):
            logger.info("{}({}): reauthorizing".format(self.name, op.name))

            # We set _active_connect_op here because reauthorizing the connection is the same as a connect for "active operation" tracking purposes.
            self._cancel_pending_connection_op()
            self._pending_connection_op = op
            self._start_connection_watchdog(op)
            try:
                self.transport.reauthorize_connection(password=self.sas_token)
            except Exception as e:
                logger.error("transport.reauthorize_connection raised error")
                logger.error(traceback.format_exc())
                self._cancel_connection_watchdog(op)
                self._pending_connection_op = None
                # Send up a DisconenctedEvent.  If we ran a ReauthorizeConnectionOperatoin,
                # some code must think we're still connected.  If we got an exception here,
                # we're not conencted, and we need to notify upper layers. (Paho should do this,
                # but it only causes a DisconnectedEvent on manual disconnect or if a PINGRESP
                # failed, and it's possible to hit this code without either of those things
                # happening.
                if isinstance(e, transport_exceptions.ConnectionDroppedError):
                    self.send_event_up(
                        pipeline_events_base.DisconnectedEvent())
                op.complete(error=e)

        elif isinstance(op, pipeline_ops_base.DisconnectOperation):
            logger.info("{}({}): disconnecting".format(self.name, op.name))

            self._cancel_pending_connection_op()
            self._pending_connection_op = op
            # We don't need a watchdog on disconnect because there's no callback to wait for
            # and we respond to a watchdog timeout by calling disconnect, which is what we're
            # already doing.

            try:
                self.transport.disconnect()
            except Exception as e:
                logger.error("transport.disconnect raised error")
                logger.error(traceback.format_exc())
                self._pending_connection_op = None
                op.complete(error=e)

        elif isinstance(op, pipeline_ops_mqtt.MQTTPublishOperation):
            logger.info("{}({}): publishing on {}".format(
                self.name, op.name, op.topic))

            @pipeline_thread.invoke_on_pipeline_thread_nowait
            def on_published():
                logger.debug("{}({}): PUBACK received. completing op.".format(
                    self.name, op.name))
                op.complete()

            try:
                self.transport.publish(topic=op.topic,
                                       payload=op.payload,
                                       callback=on_published)
            except transport_exceptions.ConnectionDroppedError:
                self.send_event_up(pipeline_events_base.DisconnectedEvent())
                raise

        elif isinstance(op, pipeline_ops_mqtt.MQTTSubscribeOperation):
            logger.info("{}({}): subscribing to {}".format(
                self.name, op.name, op.topic))

            @pipeline_thread.invoke_on_pipeline_thread_nowait
            def on_subscribed():
                logger.debug("{}({}): SUBACK received. completing op.".format(
                    self.name, op.name))
                op.complete()

            try:
                self.transport.subscribe(topic=op.topic,
                                         callback=on_subscribed)
            except transport_exceptions.ConnectionDroppedError:
                self.send_event_up(pipeline_events_base.DisconnectedEvent())
                raise

        elif isinstance(op, pipeline_ops_mqtt.MQTTUnsubscribeOperation):
            logger.info("{}({}): unsubscribing from {}".format(
                self.name, op.name, op.topic))

            @pipeline_thread.invoke_on_pipeline_thread_nowait
            def on_unsubscribed():
                logger.debug(
                    "{}({}): UNSUBACK received.  completing op.".format(
                        self.name, op.name))
                op.complete()

            try:
                self.transport.unsubscribe(topic=op.topic,
                                           callback=on_unsubscribed)
            except transport_exceptions.ConnectionDroppedError:
                self.send_event_up(pipeline_events_base.DisconnectedEvent())
                raise

        else:
            # This code block should not be reached in correct program flow.
            # This will raise an error when executed.
            self.send_op_down(op)