def __init__(self, **config): super(FedMsgContext, self).__init__() self.log = logging.getLogger("fedmsg") self.c = config self.hostname = socket.gethostname().split('.', 1)[0] # Prepare our context and publisher self.context = zmq.Context(config['io_threads']) method = ['bind', 'connect'][config['active']] # If no name is provided, use the calling module's __name__ to decide # which publishing endpoint to use. if not config.get("name", None): module_name = guess_calling_module(default="fedmsg") config["name"] = module_name + '.' + self.hostname if any(map(config["name"].startswith, ['fedmsg'])): config["name"] = None # Find my message-signing cert if I need one. if self.c.get('sign_messages', False) and config.get("name"): if not config.get("crypto_backend") == "gpg": if 'cert_prefix' in config: cert_index = "%s.%s" % (config['cert_prefix'], self.hostname) else: cert_index = config['name'] if cert_index == 'relay_inbound': cert_index = "shell.%s" % self.hostname self.c['certname'] = self.c['certnames'][cert_index] else: self.c['gpg_signing_key'] = self.c['gpg_keys'][cert_index] # Do a little special-case mangling. We never want to "listen" to the # relay_inbound address, but in the special case that we want to emit # our messages there, we add it to the :term:`endpoints` dict so that # the code below where we "Actually set up our publisher" can be # simplified. See Issue #37 - http://bit.ly/KN6dEK if config.get('active', False): # If the user has called us with "active=True" then presumably they # have given us a "name" as well. name = config.get("name", "relay_inbound") config['endpoints'][name] = config[name] # Actually set up our publisher if ( not config.get("mute", False) and config.get("name", None) and config.get("endpoints", None) and config['endpoints'].get(config['name']) ): # Construct it. self.publisher = self.context.socket(zmq.PUB) set_high_water_mark(self.publisher, config) set_tcp_keepalive(self.publisher, config) # Set a zmq_linger, thus doing a little bit more to ensure that our # message gets to the fedmsg-relay (*if* we're talking to the relay # which is the case when method == 'connect'). if method == 'connect': self.publisher.setsockopt(zmq.LINGER, config['zmq_linger']) # "Listify" our endpoints. If we're given a list, good. If we're # given a single item, turn it into a list of length 1. config['endpoints'][config['name']] = list(iterate( config['endpoints'][config['name']])) # Try endpoint after endpoint in the list of endpoints. If we # succeed in establishing one, then stop. *That* is our publishing # endpoint. _established = False for endpoint in config['endpoints'][config['name']]: if method == 'bind': endpoint = "tcp://*:{port}".format( port=endpoint.rsplit(':')[-1] ) try: # Call either bind or connect on the new publisher. # This will raise an exception if there's another process # already using the endpoint. getattr(self.publisher, method)(endpoint) # If we can do this successfully, then stop trying. _established = True break except zmq.ZMQError: # If we fail to bind or connect, there's probably another # process already using that endpoint port. Try the next # one. pass # If we make it through the loop without establishing our # connection, then there are not enough endpoints listed in the # config for the number of processes attempting to use fedmsg. if not _established: raise IOError( "Couldn't find an available endpoint " "for name %r" % config.get("name", None)) elif config.get('mute', False): # Our caller doesn't intend to send any messages. Pass silently. pass else: # Something is wrong. warnings.warn( "fedmsg is not configured to send any messages " "for name %r" % config.get("name", None)) # Cleanup. See http://bit.ly/SaGeOr for discussion. weakref.ref(threading.current_thread(), self.destroy) # Sleep just to make sure that the socket gets set up before anyone # tries anything. This is a documented zmq 'feature'. time.sleep(config['post_init_sleep'])
def publish(self, topic=None, msg=None, modname=None): """ Send a message over the publishing zeromq socket. >>> import fedmsg >>> fedmsg.publish(topic='testing', modname='test', msg={ ... 'test': "Hello World", ... }) The above snippet will send the message ``'{test: "Hello World"}'`` over the ``<topic_prefix>.dev.test.testing`` topic. This function (and other API functions) do a little bit more heavy lifting than they let on. If the "zeromq context" is not yet initialized, :func:`fedmsg.init` is called to construct it and store it as :data:`fedmsg.__local.__context` before anything else is done. The ``modname`` argument will be omitted in most use cases. By default, ``fedmsg`` will try to guess the name of the module that called it and use that to produce an intelligent topic. Specifying ``modname`` explicitly overrides this behavior. The fully qualified topic of a message is constructed out of the following pieces: <:term:`topic_prefix`>.<:term:`environment`>.<``modname``>.<``topic``> ---- **An example from Fedora Tagger -- SQLAlchemy encoding** Here's an example from `fedora-tagger <http://github.com/fedora-infra/fedora-tagger>`_ that sends the information about a new tag over ``org.fedoraproject.{dev,stg,prod}.fedoratagger.tag.update``:: >>> import fedmsg >>> fedmsg.publish(topic='tag.update', msg={ ... 'user': user, ... 'tag': tag, ... }) Note that the `tag` and `user` objects are SQLAlchemy objects defined by tagger. They both have ``.__json__()`` methods which :func:`fedmsg.publish` uses to encode both objects as stringified JSON for you. Under the hood, specifically, ``.publish`` uses :mod:`fedmsg.encoding` to do this. ``fedmsg`` has also guessed the module name (``modname``) of it's caller and inserted it into the topic for you. The code from which we stole the above snippet lives in ``fedoratagger.controllers.root``. ``fedmsg`` figured that out and stripped it down to just ``fedoratagger`` for the final topic of ``org.fedoraproject.{dev,stg,prod}.fedoratagger.tag.update``. ---- **Shell Usage** You could also use the ``fedmsg-logger`` from a shell script like so:: $ echo "Hello, world." | fedmsg-logger --topic testing $ echo '{"foo": "bar"}' | fedmsg-logger --json-input """ topic = topic or 'unspecified' msg = msg or dict() # If no modname is supplied, then guess it from the call stack. modname = modname or guess_calling_module(default="fedmsg") topic = '.'.join([modname, topic]) if topic[:len(self.c['topic_prefix'])] != self.c['topic_prefix']: topic = '.'.join([ self.c['topic_prefix'], self.c['environment'], topic, ]) if type(topic) == unicode: topic = to_bytes(topic, encoding='utf8', nonstring="passthru") year = datetime.datetime.now().year self._i += 1 msg = dict( topic=topic, msg=msg, timestamp=int(time.time()), msg_id=str(year) + '-' + str(uuid.uuid4()), i=self._i, username=getpass.getuser(), ) if self.c.get('sign_messages', False): msg = fedmsg.crypto.sign(msg, **self.c) store = self.c.get('persistent_store', None) if store: # Add the seq_id field msg = store.add(msg) self.publisher.send_multipart( [topic, fedmsg.encoding.dumps(msg)], flags=zmq.NOBLOCK, )
def publish(self, topic=None, msg=None, modname=None, **kw): """ Send a message over the publishing zeromq socket. >>> import fedmsg >>> fedmsg.publish(topic='testing', modname='test', msg={ ... 'test': "Hello World", ... }) The above snippet will send the message ``'{test: "Hello World"}'`` over the ``<topic_prefix>.dev.test.testing`` topic. This function (and other API functions) do a little bit more heavy lifting than they let on. If the "zeromq context" is not yet initialized, :func:`fedmsg.init` is called to construct it and store it as :data:`fedmsg.__local.__context` before anything else is done. The ``modname`` argument will be omitted in most use cases. By default, ``fedmsg`` will try to guess the name of the module that called it and use that to produce an intelligent topic. Specifying ``modname`` explicitly overrides this behavior. The fully qualified topic of a message is constructed out of the following pieces: <:term:`topic_prefix`>.<:term:`environment`>.<``modname``>.<``topic``> ---- **An example from Fedora Tagger -- SQLAlchemy encoding** Here's an example from `fedora-tagger <https://github.com/fedora-infra/fedora-tagger>`_ that sends the information about a new tag over ``org.fedoraproject.{dev,stg,prod}.fedoratagger.tag.update``:: >>> import fedmsg >>> fedmsg.publish(topic='tag.update', msg={ ... 'user': user, ... 'tag': tag, ... }) Note that the `tag` and `user` objects are SQLAlchemy objects defined by tagger. They both have ``.__json__()`` methods which :func:`fedmsg.publish` uses to encode both objects as stringified JSON for you. Under the hood, specifically, ``.publish`` uses :mod:`fedmsg.encoding` to do this. ``fedmsg`` has also guessed the module name (``modname``) of it's caller and inserted it into the topic for you. The code from which we stole the above snippet lives in ``fedoratagger.controllers.root``. ``fedmsg`` figured that out and stripped it down to just ``fedoratagger`` for the final topic of ``org.fedoraproject.{dev,stg,prod}.fedoratagger.tag.update``. ---- **Shell Usage** You could also use the ``fedmsg-logger`` from a shell script like so:: $ echo "Hello, world." | fedmsg-logger --topic testing $ echo '{"foo": "bar"}' | fedmsg-logger --json-input """ topic = topic or 'unspecified' msg = msg or dict() # If no modname is supplied, then guess it from the call stack. modname = modname or guess_calling_module(default="fedmsg") topic = '.'.join([modname, topic]) if topic[:len(self.c['topic_prefix'])] != self.c['topic_prefix']: topic = '.'.join([ self.c['topic_prefix'], self.c['environment'], topic, ]) if isinstance(topic, six.text_type): topic = to_bytes(topic, encoding='utf8', nonstring="passthru") year = datetime.datetime.now().year self._i += 1 msg = dict( topic=topic.decode('utf-8'), msg=msg, timestamp=int(time.time()), msg_id=str(year) + '-' + str(uuid.uuid4()), i=self._i, username=getpass.getuser(), ) # Find my message-signing cert if I need one. if self.c.get('sign_messages', False): if not self.c.get("crypto_backend") == "gpg": if 'cert_prefix' in self.c: cert_index = "%s.%s" % (self.c['cert_prefix'], self.hostname) else: cert_index = self.c['name'] if cert_index == 'relay_inbound': cert_index = "shell.%s" % self.hostname self.c['certname'] = self.c['certnames'][cert_index] else: self.c['gpg_signing_key'] = self.c['gpg_keys'][cert_index] if self.c.get('sign_messages', False): msg = fedmsg.crypto.sign(msg, **self.c) store = self.c.get('persistent_store', None) if store: # Add the seq_id field msg = store.add(msg) self.publisher.send_multipart( [topic, fedmsg.encoding.dumps(msg).encode('utf-8')], flags=zmq.NOBLOCK, )
def __init__(self, **config): super(FedMsgContext, self).__init__() self.log = logging.getLogger("fedmsg") self.c = config self.hostname = socket.gethostname().split('.', 1)[0] # Prepare our context and publisher self.context = zmq.Context(config['io_threads']) method = ['bind', 'connect'][config['active']] # If no name is provided, use the calling module's __name__ to decide # which publishing endpoint to use (unless active=True, in which case # we use "relay_inbound" as set in the subsequent code block). if not config.get("name", None) and not config.get('active', False): module_name = guess_calling_module(default="fedmsg") config["name"] = module_name + '.' + self.hostname if any(map(config["name"].startswith, ['fedmsg'])): config["name"] = None # Do a little special-case mangling. We never want to "listen" to the # relay_inbound address, but in the special case that we want to emit # our messages there, we add it to the :term:`endpoints` dict so that # the code below where we "Actually set up our publisher" can be # simplified. See Issue #37 - https://bit.ly/KN6dEK if config.get('active', False): try: name = config['name'] = config.get("name", "relay_inbound") config['endpoints'][name] = config[name] except KeyError: raise KeyError("Could not find endpoint for fedmsg-relay." " Try installing fedmsg-relay.") # Actually set up our publisher if (not config.get("mute", False) and config.get("name", None) and config.get("endpoints", None) and config['endpoints'].get(config['name'])): # Construct it. self.publisher = self.context.socket(zmq.PUB) set_high_water_mark(self.publisher, config) set_tcp_keepalive(self.publisher, config) # Set a zmq_linger, thus doing a little bit more to ensure that our # message gets to the fedmsg-relay (*if* we're talking to the relay # which is the case when method == 'connect'). if method == 'connect': self.publisher.setsockopt(zmq.LINGER, config['zmq_linger']) # "Listify" our endpoints. If we're given a list, good. If we're # given a single item, turn it into a list of length 1. config['endpoints'][config['name']] = list( iterate(config['endpoints'][config['name']])) # Try endpoint after endpoint in the list of endpoints. If we # succeed in establishing one, then stop. *That* is our publishing # endpoint. _established = False for endpoint in config['endpoints'][config['name']]: self.log.debug("Trying to %s to %s" % (method, endpoint)) if method == 'bind': endpoint = "tcp://*:{port}".format( port=endpoint.rsplit(':')[-1]) try: # Call either bind or connect on the new publisher. # This will raise an exception if there's another process # already using the endpoint. getattr(self.publisher, method)(endpoint) # If we can do this successfully, then stop trying. _established = True break except zmq.ZMQError: # If we fail to bind or connect, there's probably another # process already using that endpoint port. Try the next # one. pass # If we make it through the loop without establishing our # connection, then there are not enough endpoints listed in the # config for the number of processes attempting to use fedmsg. if not _established: raise IOError("Couldn't find an available endpoint " "for name %r" % config.get("name", None)) elif config.get('mute', False): # Our caller doesn't intend to send any messages. Pass silently. pass else: # Something is wrong. warnings.warn("fedmsg is not configured to send any messages " "for name %r" % config.get("name", None)) # Cleanup. See https://bit.ly/SaGeOr for discussion. weakref.ref(threading.current_thread(), self.destroy) # Sleep just to make sure that the socket gets set up before anyone # tries anything. This is a documented zmq 'feature'. time.sleep(config['post_init_sleep'])
def publish(self, topic=None, msg=None, modname=None, pre_fire_hook=None, **kw): """ Send a message over the publishing zeromq socket. >>> import fedmsg >>> fedmsg.publish(topic='testing', modname='test', msg={ ... 'test': "Hello World", ... }) The above snippet will send the message ``'{test: "Hello World"}'`` over the ``<topic_prefix>.dev.test.testing`` topic. This function (and other API functions) do a little bit more heavy lifting than they let on. If the "zeromq context" is not yet initialized, :func:`fedmsg.init` is called to construct it and store it as :data:`fedmsg.__local.__context` before anything else is done. The ``modname`` argument will be omitted in most use cases. By default, ``fedmsg`` will try to guess the name of the module that called it and use that to produce an intelligent topic. Specifying ``modname`` explicitly overrides this behavior. The ``pre_fire_hook`` argument may be a callable that will be called with a single argument -- the dict of the constructed message -- just before it is handed off to ZeroMQ for publication. The fully qualified topic of a message is constructed out of the following pieces: <:term:`topic_prefix`>.<:term:`environment`>.<``modname``>.<``topic``> ---- **An example from Fedora Tagger -- SQLAlchemy encoding** Here's an example from `fedora-tagger <https://github.com/fedora-infra/fedora-tagger>`_ that sends the information about a new tag over ``org.fedoraproject.{dev,stg,prod}.fedoratagger.tag.update``:: >>> import fedmsg >>> fedmsg.publish(topic='tag.update', msg={ ... 'user': user, ... 'tag': tag, ... }) Note that the `tag` and `user` objects are SQLAlchemy objects defined by tagger. They both have ``.__json__()`` methods which :func:`fedmsg.publish` uses to encode both objects as stringified JSON for you. Under the hood, specifically, ``.publish`` uses :mod:`fedmsg.encoding` to do this. ``fedmsg`` has also guessed the module name (``modname``) of it's caller and inserted it into the topic for you. The code from which we stole the above snippet lives in ``fedoratagger.controllers.root``. ``fedmsg`` figured that out and stripped it down to just ``fedoratagger`` for the final topic of ``org.fedoraproject.{dev,stg,prod}.fedoratagger.tag.update``. ---- **Shell Usage** You could also use the ``fedmsg-logger`` from a shell script like so:: $ echo "Hello, world." | fedmsg-logger --topic testing $ echo '{"foo": "bar"}' | fedmsg-logger --json-input """ topic = topic or 'unspecified' msg = msg or dict() # If no modname is supplied, then guess it from the call stack. modname = modname or guess_calling_module(default="fedmsg") topic = '.'.join([modname, topic]) if topic[:len(self.c['topic_prefix'])] != self.c['topic_prefix']: topic = '.'.join([ self.c['topic_prefix'], self.c['environment'], topic, ]) if isinstance(topic, six.text_type): topic = to_bytes(topic, encoding='utf8', nonstring="passthru") year = datetime.datetime.now().year self._i += 1 msg = dict( topic=topic.decode('utf-8'), msg=msg, timestamp=int(time.time()), msg_id=str(year) + '-' + str(uuid.uuid4()), i=self._i, username=getpass.getuser(), ) # Find my message-signing cert if I need one. if self.c.get('sign_messages', False): if not self.c.get("crypto_backend") == "gpg": if 'cert_prefix' in self.c: cert_index = "%s.%s" % (self.c['cert_prefix'], self.hostname) else: cert_index = self.c['name'] if cert_index == 'relay_inbound': cert_index = "shell.%s" % self.hostname self.c['certname'] = self.c['certnames'][cert_index] else: if 'gpg_signing_key' not in self.c: self.c['gpg_signing_key'] = self.c['gpg_keys'][self.hostname] if self.c.get('sign_messages', False): msg = fedmsg.crypto.sign(msg, **self.c) store = self.c.get('persistent_store', None) if store: # Add the seq_id field msg = store.add(msg) if pre_fire_hook: pre_fire_hook(msg) # We handle zeromq publishing ourselves. But, if that is disabled, # defer to the moksha' hub's twisted reactor to send messages (if # available). if self.c.get('zmq_enabled', True): self.publisher.send_multipart( [topic, fedmsg.encoding.dumps(msg).encode('utf-8')], flags=zmq.NOBLOCK, ) else: # Perhaps we're using STOMP or AMQP? Let moksha handle it. import moksha.hub # First, a quick sanity check. if not moksha.hub._hub: raise AttributeError("Unable to publish non-zeromq msg" "without moksha-hub initialization.") # Let moksha.hub do our work. moksha.hub._hub.send_message( topic=topic, message=fedmsg.encoding.dumps(msg).encode('utf-8'), jsonify=False, )