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")
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)
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")
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" )
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")
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)
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)