def post_fork(self, payload_handler, io_loop): ''' After forking we need to create all of the local sockets to listen to the router :param func payload_handler: A function to called to handle incoming payloads as they are picked up off the wire :param IOLoop io_loop: An instance of a Tornado IOLoop, to handle event scheduling ''' self.payload_handler = payload_handler self.io_loop = io_loop self.context = zmq.Context(1) self._socket = self.context.socket(zmq.REP) self._start_zmq_monitor() if self.opts.get('ipc_mode', '') == 'tcp': self.w_uri = 'tcp://127.0.0.1:{0}'.format( self.opts.get('tcp_master_workers', 4515) ) else: self.w_uri = 'ipc://{0}'.format( os.path.join(self.opts['sock_dir'], 'workers.ipc') ) log.info('Worker binding to socket %s', self.w_uri) self._socket.connect(self.w_uri) salt.transport.mixins.auth.AESReqServerMixin.post_fork(self, payload_handler, io_loop) self.stream = zmq.eventloop.zmqstream.ZMQStream(self._socket, io_loop=self.io_loop) self.stream.on_recv_stream(self.handle_message)
def post_fork(self, message_handler, io_loop): """ After forking we need to create all of the local sockets to listen to the router :param func message_handler: A function to called to handle incoming payloads as they are picked up off the wire :param IOLoop io_loop: An instance of a Tornado IOLoop, to handle event scheduling """ context = zmq.Context(1) self._socket = context.socket(zmq.REP) # Linger -1 means we'll never discard messages. self._socket.setsockopt(zmq.LINGER, -1) self._start_zmq_monitor() if self.opts.get("ipc_mode", "") == "tcp": self.w_uri = "tcp://127.0.0.1:{}".format( self.opts.get("tcp_master_workers", 4515)) else: self.w_uri = "ipc://{}".format( os.path.join(self.opts["sock_dir"], "workers.ipc")) log.info("Worker binding to socket %s", self.w_uri) self._socket.connect(self.w_uri) self.stream = zmq.eventloop.zmqstream.ZMQStream(self._socket, io_loop=io_loop) self.message_handler = message_handler self.stream.on_recv_stream(self.handle_message)
def __init__(self, opts, addr, linger=0, io_loop=None): """ Create an asynchronous message client :param dict opts: The salt opts dictionary :param str addr: The interface IP address to bind to :param int linger: The number of seconds to linger on a ZMQ socket. See http://api.zeromq.org/2-1:zmq-setsockopt [ZMQ_LINGER] :param IOLoop io_loop: A Tornado IOLoop event scheduler [tornado.ioloop.IOLoop] """ self.opts = opts self.addr = addr self.linger = linger if io_loop is None: self.io_loop = salt.ext.tornado.ioloop.IOLoop.current() else: self.io_loop = io_loop self.context = zmq.Context() self.send_queue = [] # mapping of message -> future self.send_future_map = {} self._closing = False
def run(self): ''' Start the ret port binding ''' self.context = zmq.Context(self.opts['worker_threads']) self.uri = 'tcp://{interface}:{ret_port}'.format(**self.opts) log.info('ZMQ Ret port binding to %s', self.uri) self.clients = self.context.socket(zmq.ROUTER) if self.opts['ipv6'] is True and hasattr(zmq, 'IPV4ONLY'): # IPv6 sockets work for both IPv6 and IPv4 addresses self.clients.setsockopt(zmq.IPV4ONLY, 0) try: self.clients.setsockopt(zmq.HWM, self.opts['rep_hwm']) except AttributeError: self.clients.setsockopt(zmq.SNDHWM, self.opts['rep_hwm']) self.clients.setsockopt(zmq.RCVHWM, self.opts['rep_hwm']) self.clients.setsockopt(zmq.BACKLOG, self.opts['zmq_backlog']) self.workers = self.context.socket(zmq.DEALER) self.w_uri = 'ipc://{0}'.format( os.path.join(self.opts['sock_dir'], 'workers.ipc')) log.info('Setting up the master communication server') self.clients.bind(self.uri) self.workers.bind(self.w_uri) while True: try: zmq.device(zmq.QUEUE, self.clients, self.workers) except zmq.ZMQError as exc: if exc.errno == errno.EINTR: continue six.reraise(*sys.exc_info())
def __init__(self, opts, addr, linger=0, io_loop=None): ''' Create an asynchronous message client :param dict opts: The salt opts dictionary :param str addr: The interface IP address to bind to :param int linger: The number of seconds to linger on a ZMQ socket. See http://api.zeromq.org/2-1:zmq-setsockopt [ZMQ_LINGER] :param IOLoop io_loop: A Tornado IOLoop event scheduler [tornado.ioloop.IOLoop] ''' self.opts = opts self.addr = addr self.linger = linger if io_loop is None: install_zmq() ZMQDefaultLoop.current() else: self.io_loop = io_loop self.serial = salt.payload.Serial(self.opts) self.context = zmq.Context() # wire up sockets self._init_socket() self.send_queue = [] # mapping of message -> future self.send_future_map = {} self.send_timeout_map = {} # message -> timeout
def _zmq(address, port, **kwargs): context = zmq.Context() socket = context.socket(zmq.SUB) if salt.utils.network.is_ipv6(address): socket.ipv6 = True socket.connect("tcp://{addr}:{port}".format(addr=address, port=port)) socket.setsockopt(zmq.SUBSCRIBE, b"") return socket.recv
def publish_daemon( self, publish_payload, presence_callback=None, remove_presence_callback=None, ): """ This method represents the Publish Daemon process. It is intended to be run in a thread or process as it creates and runs an it's own ioloop. """ ioloop = salt.ext.tornado.ioloop.IOLoop() ioloop.make_current() self.io_loop = ioloop context = zmq.Context(1) pub_sock = context.socket(zmq.PUB) monitor = ZeroMQSocketMonitor(pub_sock) monitor.start_io_loop(ioloop) _set_tcp_keepalive(pub_sock, self.opts) self.dpub_sock = pub_sock = zmq.eventloop.zmqstream.ZMQStream(pub_sock) # if 2.1 >= zmq < 3.0, we only have one HWM setting try: pub_sock.setsockopt(zmq.HWM, self.opts.get("pub_hwm", 1000)) # in zmq >= 3.0, there are separate send and receive HWM settings except (AttributeError, zmq.error.ZMQError): # Set the High Water Marks. For more information on HWM, see: # http://api.zeromq.org/4-1:zmq-setsockopt pub_sock.setsockopt(zmq.SNDHWM, self.opts.get("pub_hwm", 1000)) pub_sock.setsockopt(zmq.RCVHWM, self.opts.get("pub_hwm", 1000)) if self.opts["ipv6"] is True and hasattr(zmq, "IPV4ONLY"): # IPv6 sockets work for both IPv6 and IPv4 addresses pub_sock.setsockopt(zmq.IPV4ONLY, 0) pub_sock.setsockopt(zmq.BACKLOG, self.opts.get("zmq_backlog", 1000)) pub_sock.setsockopt(zmq.LINGER, -1) # Prepare minion pull socket pull_sock = context.socket(zmq.PULL) pull_sock = zmq.eventloop.zmqstream.ZMQStream(pull_sock) pull_sock.setsockopt(zmq.LINGER, -1) salt.utils.zeromq.check_ipc_path_max_len(self.pull_uri) # Start the minion command publisher log.info("Starting the Salt Publisher on %s", self.pub_uri) pub_sock.bind(self.pub_uri) # Securely create socket log.info("Starting the Salt Puller on %s", self.pull_uri) with salt.utils.files.set_umask(0o177): pull_sock.bind(self.pull_uri) @salt.ext.tornado.gen.coroutine def on_recv(packages): for package in packages: payload = salt.payload.loads(package) yield publish_payload(payload) pull_sock.on_recv(on_recv) try: ioloop.start() finally: pub_sock.close() pull_sock.close()
def publish(self, load): ''' Publish "load" to minions :param dict load: A load to be sent across the wire to minions ''' payload = {'enc': 'aes'} crypticle = salt.crypt.Crypticle( self.opts, salt.master.SMaster.secrets['aes']['secret'].value) payload['load'] = crypticle.dumps(load) if self.opts['sign_pub_messages']: master_pem_path = os.path.join(self.opts['pki_dir'], 'master.pem') log.debug("Signing data packet") payload['sig'] = salt.crypt.sign_message(master_pem_path, payload['load']) # Send 0MQ to the publisher context = zmq.Context(1) pub_sock = context.socket(zmq.PUSH) if self.opts.get('ipc_mode', '') == 'tcp': pull_uri = 'tcp://127.0.0.1:{0}'.format( self.opts.get('tcp_master_publish_pull', 4514)) else: pull_uri = 'ipc://{0}'.format( os.path.join(self.opts['sock_dir'], 'publish_pull.ipc')) pub_sock.connect(pull_uri) int_payload = {'payload': self.serial.dumps(payload)} # add some targeting stuff for lists only (for now) if load['tgt_type'] == 'list': int_payload['topic_lst'] = load['tgt'] # If zmq_filtering is enabled, target matching has to happen master side match_targets = ["pcre", "glob", "list"] if self.opts['zmq_filtering'] and load['tgt_type'] in match_targets: # Fetch a list of minions that match _res = self.ckminions.check_minions(load['tgt'], tgt_type=load['tgt_type']) match_ids = _res['minions'] log.debug("Publish Side Match: %s", match_ids) # Send list of miions thru so zmq can target them int_payload['topic_lst'] = match_ids payload = self.serial.dumps(int_payload) log.debug( 'Sending payload to publish daemon. jid=%s size=%d', load.get('jid', None), len(payload), ) pub_sock.send(payload) log.debug('Sent payload to publish daemon.') pub_sock.close() context.term()
def _proxy_logs_target(self, socket_bind_event): context = zmq.Context() out_proxy = pusher = None try: out_proxy = context.socket(zmq.PAIR) out_proxy.set_hwm(100000) proxy_port = out_proxy.bind_to_random_port("tcp://127.0.0.1") self.proxy_address = "tcp://127.0.0.1:{}".format(proxy_port) except zmq.ZMQError as exc: if out_proxy is not None: out_proxy.close(1000) context.term() sys.stderr.write( "Failed to bind the ZMQ PAIR socket: {}\n{}\n".format( exc, traceback.format_exc(exc))) sys.stderr.flush() return try: pusher = context.socket(zmq.PUSH) pusher.set_hwm(100000) pusher.connect(self.push_address) except zmq.ZMQError as exc: if pusher is not None: pusher.close(1000) context.term() sys.stderr.write( "Failed to connect the ZMQ PUSH socket: {}\n{}\n".format( exc, traceback.format_exc(exc))) sys.stderr.flush() socket_bind_event.set() sentinel = salt.utils.msgpack.dumps(None) while True: try: msg = out_proxy.recv() if msg == sentinel: # Received sentinel to stop break pusher.send(msg) except zmq.ZMQError as exc: sys.stderr.write( "Failed to proxy log message: {}\n{}\n".format( exc, traceback.format_exc(exc))) sys.stderr.flush() break # Close the receiving end of the PAIR proxy socket out_proxy.close(0) # Allow, the pusher queue to send any messsges in it's queue for # the next 1.5 seconds pusher.close(1500) context.term()
def zmq_device(self): """ Multiprocessing target for the zmq queue device """ self.__setup_signals() context = zmq.Context(self.opts["worker_threads"]) # Prepare the zeromq sockets self.uri = "tcp://{interface}:{ret_port}".format(**self.opts) self.clients = context.socket(zmq.ROUTER) self.clients.setsockopt(zmq.LINGER, -1) if self.opts["ipv6"] is True and hasattr(zmq, "IPV4ONLY"): # IPv6 sockets work for both IPv6 and IPv4 addresses self.clients.setsockopt(zmq.IPV4ONLY, 0) self.clients.setsockopt(zmq.BACKLOG, self.opts.get("zmq_backlog", 1000)) self._start_zmq_monitor() self.workers = context.socket(zmq.DEALER) self.workers.setsockopt(zmq.LINGER, -1) if self.opts[ "mworker_queue_niceness"] and not salt.utils.platform.is_windows( ): log.info( "setting mworker_queue niceness to %d", self.opts["mworker_queue_niceness"], ) os.nice(self.opts["mworker_queue_niceness"]) if self.opts.get("ipc_mode", "") == "tcp": self.w_uri = "tcp://127.0.0.1:{}".format( self.opts.get("tcp_master_workers", 4515)) else: self.w_uri = "ipc://{}".format( os.path.join(self.opts["sock_dir"], "workers.ipc")) log.info("Setting up the master communication server") log.error("ReqServer clients %s", self.uri) self.clients.bind(self.uri) log.error("ReqServer workers %s", self.w_uri) self.workers.bind(self.w_uri) while True: if self.clients.closed or self.workers.closed: break try: zmq.device(zmq.QUEUE, self.clients, self.workers) except zmq.ZMQError as exc: if exc.errno == errno.EINTR: continue raise except (KeyboardInterrupt, SystemExit): break context.term()
def start(self): if self.pid != os.getpid(): self.stop() self._exiting = False if self._exiting is True: return if self.in_proxy is not None: return atexit.register(self.stop) context = in_proxy = None try: context = zmq.Context() self.context = context except zmq.ZMQError as exc: sys.stderr.write( "Failed to create the ZMQ Context: {}\n{}\n".format( exc, traceback.format_exc(exc))) sys.stderr.flush() # Let's start the proxy thread socket_bind_event = threading.Event() self.proxy_thread = threading.Thread(target=self._proxy_logs_target, args=(socket_bind_event, )) self.proxy_thread.daemon = True self.proxy_thread.start() # Now that we discovered which random port to use, lest's continue with the setup if socket_bind_event.wait(5) is not True: sys.stderr.write("Failed to bind the ZMQ socket PAIR\n") sys.stderr.flush() context.term() return # And we can now also connect the messages input side of the proxy try: in_proxy = self.context.socket(zmq.PAIR) in_proxy.set_hwm(100000) in_proxy.connect(self.proxy_address) self.in_proxy = in_proxy except zmq.ZMQError as exc: if in_proxy is not None: in_proxy.close(1000) sys.stderr.write( "Failed to bind the ZMQ PAIR socket: {}\n{}\n".format( exc, traceback.format_exc(exc))) sys.stderr.flush()
def run(self): """ main loop that fires the event every second """ context = zmq.Context() # the socket for outgoing timer events socket = context.socket(zmq.PUB) socket.setsockopt(zmq.LINGER, 100) socket.bind("ipc://" + self.timer_sock) count = 0 log.debug("ConCache-Timer started") while not self.stopped.wait(1): socket.send(salt.payload.dumps(count)) count += 1 if count >= 60: count = 0
def run(self): ''' main loop that fires the event every second ''' context = zmq.Context() # the socket for outgoing timer events socket = context.socket(zmq.PUB) socket.setsockopt(zmq.LINGER, 100) socket.bind('ipc://' + self.timer_sock) count = 0 log.debug('ConCache-Timer started') while not self.stopped.wait(1): socket.send(self.serial.dumps(count)) count += 1 if count >= 60: count = 0
def pub_connect(self): """ Create and connect this thread's zmq socket. If a publisher socket already exists "pub_close" is called before creating and connecting a new socket. """ if self.pub_sock: self.pub_close() ctx = zmq.Context() self._sock_data.sock = ctx.socket(zmq.PUSH) self.pub_sock.setsockopt(zmq.LINGER, -1) if self.opts.get("ipc_mode", "") == "tcp": pull_uri = "tcp://127.0.0.1:{}".format( self.opts.get("tcp_master_publish_pull", 4514)) else: pull_uri = "ipc://{}".format( os.path.join(self.opts["sock_dir"], "publish_pull.ipc")) log.debug("Connecting to pub server: %s", pull_uri) self.pub_sock.connect(pull_uri) return self._sock_data.sock
def __init__(self, opts): """ Sets up the zmq-connection to the ConCache """ self.opts = opts self.serial = salt.payload.Serial(self.opts.get("serial", "")) self.cache_sock = os.path.join(self.opts["sock_dir"], "con_cache.ipc") self.cache_upd_sock = os.path.join(self.opts["sock_dir"], "con_upd.ipc") context = zmq.Context() # the socket for talking to the cache self.creq_out = context.socket(zmq.REQ) self.creq_out.setsockopt(zmq.LINGER, 100) self.creq_out.connect("ipc://" + self.cache_sock) # the socket for sending updates to the cache self.cupd_out = context.socket(zmq.PUB) self.cupd_out.setsockopt(zmq.LINGER, 1) self.cupd_out.connect("ipc://" + self.cache_upd_sock)
def zmq_device(self): ''' Multiprocessing target for the zmq queue device ''' self.__setup_signals() salt.utils.process.appendproctitle('MWorkerQueue') self.context = zmq.Context(self.opts['worker_threads']) # Prepare the zeromq sockets self.uri = 'tcp://{interface}:{ret_port}'.format(**self.opts) self.clients = self.context.socket(zmq.ROUTER) if self.opts['ipv6'] is True and hasattr(zmq, 'IPV4ONLY'): # IPv6 sockets work for both IPv6 and IPv4 addresses self.clients.setsockopt(zmq.IPV4ONLY, 0) self.clients.setsockopt(zmq.BACKLOG, self.opts.get('zmq_backlog', 1000)) self._start_zmq_monitor() self.workers = self.context.socket(zmq.DEALER) if self.opts.get('ipc_mode', '') == 'tcp': self.w_uri = 'tcp://127.0.0.1:{0}'.format( self.opts.get('tcp_master_workers', 4515) ) else: self.w_uri = 'ipc://{0}'.format( os.path.join(self.opts['sock_dir'], 'workers.ipc') ) log.info('Setting up the master communication server') self.clients.bind(self.uri) self.workers.bind(self.w_uri) while True: if self.clients.closed or self.workers.closed: break try: zmq.device(zmq.QUEUE, self.clients, self.workers) except zmq.ZMQError as exc: if exc.errno == errno.EINTR: continue raise exc except (KeyboardInterrupt, SystemExit): break
def __init__(self, opts): ''' Sets up the zmq-connection to the ConCache ''' self.opts = opts self.serial = salt.payload.Serial(self.opts.get('serial', '')) self.cache_sock = os.path.join(self.opts['sock_dir'], 'con_cache.ipc') self.cache_upd_sock = os.path.join( self.opts['sock_dir'], 'con_upd.ipc') context = zmq.Context() # the socket for talking to the cache self.creq_out = context.socket(zmq.REQ) self.creq_out.setsockopt(zmq.LINGER, 100) self.creq_out.connect('ipc://' + self.cache_sock) # the socket for sending updates to the cache self.cupd_out = context.socket(zmq.PUB) self.cupd_out.setsockopt(zmq.LINGER, 1) self.cupd_out.connect('ipc://' + self.cache_upd_sock)
def run(self): ''' Main loop of the ConCache, starts updates in intervals and answers requests from the MWorkers ''' context = zmq.Context() # the socket for incoming cache requests creq_in = context.socket(zmq.REP) creq_in.setsockopt(zmq.LINGER, 100) creq_in.bind('ipc://' + self.cache_sock) # the socket for incoming cache-updates from workers cupd_in = context.socket(zmq.SUB) cupd_in.setsockopt(zmq.SUBSCRIBE, b'') cupd_in.setsockopt(zmq.LINGER, 100) cupd_in.bind('ipc://' + self.update_sock) # the socket for the timer-event timer_in = context.socket(zmq.SUB) timer_in.setsockopt(zmq.SUBSCRIBE, b'') timer_in.setsockopt(zmq.LINGER, 100) timer_in.connect('ipc://' + self.upd_t_sock) poller = zmq.Poller() poller.register(creq_in, zmq.POLLIN) poller.register(cupd_in, zmq.POLLIN) poller.register(timer_in, zmq.POLLIN) # our serializer serial = salt.payload.Serial(self.opts.get('serial', '')) # register a signal handler signal.signal(signal.SIGINT, self.signal_handler) # secure the sockets from the world self.secure() log.info('ConCache started') while self.running: # we check for new events with the poller try: socks = dict(poller.poll(1)) except KeyboardInterrupt: self.stop() except zmq.ZMQError as zmq_err: log.error('ConCache ZeroMQ-Error occurred') log.exception(zmq_err) self.stop() # check for next cache-request if socks.get(creq_in) == zmq.POLLIN: msg = serial.loads(creq_in.recv()) log.debug('ConCache Received request: %s', msg) # requests to the minion list are send as str's if isinstance(msg, six.string_types): if msg == 'minions': # Send reply back to client reply = serial.dumps(self.minions) creq_in.send(reply) # check for next cache-update from workers if socks.get(cupd_in) == zmq.POLLIN: new_c_data = serial.loads(cupd_in.recv()) # tell the worker to exit #cupd_in.send(serial.dumps('ACK')) # check if the returned data is usable if not isinstance(new_c_data, list): log.error('ConCache Worker returned unusable result') del new_c_data continue # the cache will receive lists of minions # 1. if the list only has 1 item, its from an MWorker, we append it # 2. if the list contains another list, its from a CacheWorker and # the currently cached minions are replaced with that list # 3. anything else is considered malformed try: if not new_c_data: log.debug('ConCache Got empty update from worker') continue data = new_c_data[0] if isinstance(data, six.string_types): if data not in self.minions: log.debug('ConCache Adding minion %s to cache', new_c_data[0]) self.minions.append(data) elif isinstance(data, list): log.debug('ConCache Replacing minion list from worker') self.minions = data except IndexError: log.debug('ConCache Got malformed result dict from worker') del new_c_data log.info('ConCache %s entries in cache', len(self.minions)) # check for next timer-event to start new jobs if socks.get(timer_in) == zmq.POLLIN: sec_event = serial.loads(timer_in.recv()) # update the list every 30 seconds if int(sec_event % 30) == 0: cw = CacheWorker(self.opts) cw.start() self.stop() creq_in.close() cupd_in.close() timer_in.close() context.term() log.debug('ConCache Shutting down')
def _publish_daemon(self, log_queue=None): """ Bind to the interface specified in the configuration file """ salt.utils.process.appendproctitle(self.__class__.__name__) if self.opts["pub_server_niceness"] and not salt.utils.platform.is_windows(): log.info( "setting Publish daemon niceness to %i", self.opts["pub_server_niceness"], ) os.nice(self.opts["pub_server_niceness"]) if log_queue: salt.log.setup.set_multiprocessing_logging_queue(log_queue) salt.log.setup.setup_multiprocessing_logging(log_queue) # Set up the context context = zmq.Context(1) # Prepare minion publish socket pub_sock = context.socket(zmq.PUB) _set_tcp_keepalive(pub_sock, self.opts) # if 2.1 >= zmq < 3.0, we only have one HWM setting try: pub_sock.setsockopt(zmq.HWM, self.opts.get("pub_hwm", 1000)) # in zmq >= 3.0, there are separate send and receive HWM settings except AttributeError: # Set the High Water Marks. For more information on HWM, see: # http://api.zeromq.org/4-1:zmq-setsockopt pub_sock.setsockopt(zmq.SNDHWM, self.opts.get("pub_hwm", 1000)) pub_sock.setsockopt(zmq.RCVHWM, self.opts.get("pub_hwm", 1000)) if self.opts["ipv6"] is True and hasattr(zmq, "IPV4ONLY"): # IPv6 sockets work for both IPv6 and IPv4 addresses pub_sock.setsockopt(zmq.IPV4ONLY, 0) pub_sock.setsockopt(zmq.BACKLOG, self.opts.get("zmq_backlog", 1000)) pub_sock.setsockopt(zmq.LINGER, -1) pub_uri = "tcp://{interface}:{publish_port}".format(**self.opts) # Prepare minion pull socket pull_sock = context.socket(zmq.PULL) pull_sock.setsockopt(zmq.LINGER, -1) if self.opts.get("ipc_mode", "") == "tcp": pull_uri = "tcp://127.0.0.1:{}".format( self.opts.get("tcp_master_publish_pull", 4514) ) else: pull_uri = "ipc://{}".format( os.path.join(self.opts["sock_dir"], "publish_pull.ipc") ) salt.utils.zeromq.check_ipc_path_max_len(pull_uri) # Start the minion command publisher log.info("Starting the Salt Publisher on %s", pub_uri) pub_sock.bind(pub_uri) # Securely create socket log.info("Starting the Salt Puller on %s", pull_uri) with salt.utils.files.set_umask(0o177): pull_sock.bind(pull_uri) try: while True: # Catch and handle EINTR from when this process is sent # SIGUSR1 gracefully so we don't choke and die horribly try: log.debug("Publish daemon getting data from puller %s", pull_uri) package = pull_sock.recv() log.debug("Publish daemon received payload. size=%d", len(package)) unpacked_package = salt.payload.unpackage(package) unpacked_package = salt.transport.frame.decode_embedded_strs( unpacked_package ) payload = unpacked_package["payload"] log.trace("Accepted unpacked package from puller") if self.opts["zmq_filtering"]: # if you have a specific topic list, use that if "topic_lst" in unpacked_package: for topic in unpacked_package["topic_lst"]: log.trace( "Sending filtered data over publisher %s", pub_uri ) # zmq filters are substring match, hash the topic # to avoid collisions htopic = salt.utils.stringutils.to_bytes( hashlib.sha1( salt.utils.stringutils.to_bytes(topic) ).hexdigest() ) pub_sock.send(htopic, flags=zmq.SNDMORE) pub_sock.send(payload) log.trace("Filtered data has been sent") # Syndic broadcast if self.opts.get("order_masters"): log.trace("Sending filtered data to syndic") pub_sock.send(b"syndic", flags=zmq.SNDMORE) pub_sock.send(payload) log.trace("Filtered data has been sent to syndic") # otherwise its a broadcast else: # TODO: constants file for "broadcast" log.trace( "Sending broadcasted data over publisher %s", pub_uri ) pub_sock.send(b"broadcast", flags=zmq.SNDMORE) pub_sock.send(payload) log.trace("Broadcasted data has been sent") else: log.trace( "Sending ZMQ-unfiltered data over publisher %s", pub_uri ) pub_sock.send(payload) log.trace("Unfiltered data has been sent") except zmq.ZMQError as exc: if exc.errno == errno.EINTR: continue raise except KeyboardInterrupt: log.trace("Publish daemon caught Keyboard interupt, tearing down") # Cleanly close the sockets if we're shutting down if pub_sock.closed is False: pub_sock.close() if pull_sock.closed is False: pull_sock.close() if context.closed is False: context.term()
def action(self): ''' Create the publish port if it is not available and then publish the messages on it ''' if not self.zmq_behavior: return if not self.created: self.context = zmq.Context(1) self.pub_sock = self.context.socket(zmq.PUB) # if 2.1 >= zmq < 3.0, we only have one HWM setting try: self.pub_sock.setsockopt(zmq.HWM, self.opts.value.get('pub_hwm', 1000)) # in zmq >= 3.0, there are separate send and receive HWM settings except AttributeError: self.pub_sock.setsockopt(zmq.SNDHWM, self.opts.value.get('pub_hwm', 1000)) self.pub_sock.setsockopt(zmq.RCVHWM, self.opts.value.get('pub_hwm', 1000)) if self.opts.value['ipv6'] is True and hasattr(zmq, 'IPV4ONLY'): # IPv6 sockets work for both IPv6 and IPv4 addresses self.pub_sock.setsockopt(zmq.IPV4ONLY, 0) self.pub_sock.setsockopt(zmq.BACKLOG, self.opts.get('zmq_backlog', 1000)) self.pub_uri = 'tcp://{interface}:{publish_port}'.format( **self.opts.value) log.info('Starting the Salt ZeroMQ Publisher on %s', self.pub_uri) self.pub_sock.bind(self.pub_uri) self.created = True # Don't pop the publish messages! The raet behavior still needs them try: for package in self.publish.value: payload = {'enc': 'aes'} payload['load'] = self.crypticle.value.dumps( package['return']['pub']) if self.opts.value['sign_pub_messages']: master_pem_path = os.path.join(self.opts.value['pki_dir'], 'master.pem') log.debug('Signing data packet for publish') payload['sig'] = salt.crypt.sign_message( master_pem_path, payload['load']) send_payload = self.serial.dumps(payload) if self.opts.value['zmq_filtering']: # if you have a specific topic list, use that if package['return']['pub']['tgt_type'] == 'list': for topic in package['return']['pub']['tgt']: # zmq filters are substring match, hash the topic # to avoid collisions htopic = hashlib.sha1(topic).hexdigest() self.pub_sock.send(htopic, flags=zmq.SNDMORE) self.pub_sock.send(send_payload) # otherwise its a broadcast else: self.pub_sock.send('broadcast', flags=zmq.SNDMORE) self.pub_sock.send(send_payload) else: self.pub_sock.send(send_payload) except zmq.ZMQError as exc: if exc.errno == errno.EINTR: return six.reraise(*sys.exc_info())
def __init__(self, opts, **kwargs): self.opts = opts self.ttype = 'zeromq' self.io_loop = kwargs.get('io_loop') if self.io_loop is None: install_zmq() self.io_loop = ZMQDefaultLoop.current() self.hexid = hashlib.sha1(salt.utils.stringutils.to_bytes(self.opts['id'])).hexdigest() self.auth = salt.crypt.AsyncAuth(self.opts, io_loop=self.io_loop) self.serial = salt.payload.Serial(self.opts) self.context = zmq.Context() self._socket = self.context.socket(zmq.SUB) if self.opts['zmq_filtering']: # TODO: constants file for "broadcast" self._socket.setsockopt(zmq.SUBSCRIBE, b'broadcast') if self.opts.get('__role') == 'syndic': self._socket.setsockopt(zmq.SUBSCRIBE, b'syndic') else: self._socket.setsockopt( zmq.SUBSCRIBE, salt.utils.stringutils.to_bytes(self.hexid) ) else: self._socket.setsockopt(zmq.SUBSCRIBE, b'') self._socket.setsockopt(zmq.IDENTITY, salt.utils.stringutils.to_bytes(self.opts['id'])) # TODO: cleanup all the socket opts stuff if hasattr(zmq, 'TCP_KEEPALIVE'): self._socket.setsockopt( zmq.TCP_KEEPALIVE, self.opts['tcp_keepalive'] ) self._socket.setsockopt( zmq.TCP_KEEPALIVE_IDLE, self.opts['tcp_keepalive_idle'] ) self._socket.setsockopt( zmq.TCP_KEEPALIVE_CNT, self.opts['tcp_keepalive_cnt'] ) self._socket.setsockopt( zmq.TCP_KEEPALIVE_INTVL, self.opts['tcp_keepalive_intvl'] ) recon_delay = self.opts['recon_default'] if self.opts['recon_randomize']: recon_delay = randint(self.opts['recon_default'], self.opts['recon_default'] + self.opts['recon_max']) log.debug( "Generated random reconnect delay between '%sms' and '%sms' (%s)", self.opts['recon_default'], self.opts['recon_default'] + self.opts['recon_max'], recon_delay ) log.debug("Setting zmq_reconnect_ivl to '%sms'", recon_delay) self._socket.setsockopt(zmq.RECONNECT_IVL, recon_delay) if hasattr(zmq, 'RECONNECT_IVL_MAX'): log.debug( "Setting zmq_reconnect_ivl_max to '%sms'", self.opts['recon_default'] + self.opts['recon_max'] ) self._socket.setsockopt( zmq.RECONNECT_IVL_MAX, self.opts['recon_max'] ) if (self.opts['ipv6'] is True or ':' in self.opts['master_ip']) and hasattr(zmq, 'IPV4ONLY'): # IPv6 sockets work for both IPv6 and IPv4 addresses self._socket.setsockopt(zmq.IPV4ONLY, 0) if HAS_ZMQ_MONITOR and self.opts['zmq_monitor']: self._monitor = ZeroMQSocketMonitor(self._socket) self._monitor.start_io_loop(self.io_loop)
def __init__(self, opts, io_loop, **kwargs): super().__init__(opts, io_loop, **kwargs) self.opts = opts self.io_loop = io_loop self.hexid = hashlib.sha1( salt.utils.stringutils.to_bytes(self.opts["id"])).hexdigest() self._closing = False self.context = zmq.Context() self._socket = self.context.socket(zmq.SUB) if self.opts["zmq_filtering"]: # TODO: constants file for "broadcast" self._socket.setsockopt(zmq.SUBSCRIBE, b"broadcast") if self.opts.get("__role") == "syndic": self._socket.setsockopt(zmq.SUBSCRIBE, b"syndic") else: self._socket.setsockopt( zmq.SUBSCRIBE, salt.utils.stringutils.to_bytes(self.hexid)) else: self._socket.setsockopt(zmq.SUBSCRIBE, b"") self._socket.setsockopt( zmq.IDENTITY, salt.utils.stringutils.to_bytes(self.opts["id"])) # TODO: cleanup all the socket opts stuff if hasattr(zmq, "TCP_KEEPALIVE"): self._socket.setsockopt(zmq.TCP_KEEPALIVE, self.opts["tcp_keepalive"]) self._socket.setsockopt(zmq.TCP_KEEPALIVE_IDLE, self.opts["tcp_keepalive_idle"]) self._socket.setsockopt(zmq.TCP_KEEPALIVE_CNT, self.opts["tcp_keepalive_cnt"]) self._socket.setsockopt(zmq.TCP_KEEPALIVE_INTVL, self.opts["tcp_keepalive_intvl"]) recon_delay = self.opts["recon_default"] if self.opts["recon_randomize"]: recon_delay = randint( self.opts["recon_default"], self.opts["recon_default"] + self.opts["recon_max"], ) log.debug( "Generated random reconnect delay between '%sms' and '%sms' (%s)", self.opts["recon_default"], self.opts["recon_default"] + self.opts["recon_max"], recon_delay, ) log.debug("Setting zmq_reconnect_ivl to '%sms'", recon_delay) self._socket.setsockopt(zmq.RECONNECT_IVL, recon_delay) if hasattr(zmq, "RECONNECT_IVL_MAX"): log.debug( "Setting zmq_reconnect_ivl_max to '%sms'", self.opts["recon_default"] + self.opts["recon_max"], ) self._socket.setsockopt(zmq.RECONNECT_IVL_MAX, self.opts["recon_max"]) if (self.opts["ipv6"] is True or ":" in self.opts["master_ip"]) and hasattr(zmq, "IPV4ONLY"): # IPv6 sockets work for both IPv6 and IPv4 addresses self._socket.setsockopt(zmq.IPV4ONLY, 0) if HAS_ZMQ_MONITOR and self.opts["zmq_monitor"]: self._monitor = ZeroMQSocketMonitor(self._socket) self._monitor.start_io_loop(self.io_loop)
def _publish_daemon(self): ''' Bind to the interface specified in the configuration file ''' salt.utils.process.appendproctitle(self.__class__.__name__) # Set up the context context = zmq.Context(1) # Prepare minion publish socket pub_sock = context.socket(zmq.PUB) _set_tcp_keepalive(pub_sock, self.opts) # if 2.1 >= zmq < 3.0, we only have one HWM setting try: pub_sock.setsockopt(zmq.HWM, self.opts.get('pub_hwm', 1000)) # in zmq >= 3.0, there are separate send and receive HWM settings except AttributeError: # Set the High Water Marks. For more information on HWM, see: # http://api.zeromq.org/4-1:zmq-setsockopt pub_sock.setsockopt(zmq.SNDHWM, self.opts.get('pub_hwm', 1000)) pub_sock.setsockopt(zmq.RCVHWM, self.opts.get('pub_hwm', 1000)) if self.opts['ipv6'] is True and hasattr(zmq, 'IPV4ONLY'): # IPv6 sockets work for both IPv6 and IPv4 addresses pub_sock.setsockopt(zmq.IPV4ONLY, 0) pub_sock.setsockopt(zmq.BACKLOG, self.opts.get('zmq_backlog', 1000)) pub_uri = 'tcp://{interface}:{publish_port}'.format(**self.opts) # Prepare minion pull socket pull_sock = context.socket(zmq.PULL) if self.opts.get('ipc_mode', '') == 'tcp': pull_uri = 'tcp://127.0.0.1:{0}'.format( self.opts.get('tcp_master_publish_pull', 4514) ) else: pull_uri = 'ipc://{0}'.format( os.path.join(self.opts['sock_dir'], 'publish_pull.ipc') ) salt.utils.zeromq.check_ipc_path_max_len(pull_uri) # Start the minion command publisher log.info('Starting the Salt Publisher on %s', pub_uri) pub_sock.bind(pub_uri) # Securely create socket log.info('Starting the Salt Puller on %s', pull_uri) with salt.utils.files.set_umask(0o177): pull_sock.bind(pull_uri) try: while True: # Catch and handle EINTR from when this process is sent # SIGUSR1 gracefully so we don't choke and die horribly try: log.trace('Getting data from puller %s', pull_uri) package = pull_sock.recv() unpacked_package = salt.payload.unpackage(package) if six.PY3: unpacked_package = salt.transport.frame.decode_embedded_strs(unpacked_package) payload = unpacked_package['payload'] log.trace('Accepted unpacked package from puller') if self.opts['zmq_filtering']: # if you have a specific topic list, use that if 'topic_lst' in unpacked_package: for topic in unpacked_package['topic_lst']: log.trace('Sending filtered data over publisher %s', pub_uri) # zmq filters are substring match, hash the topic # to avoid collisions htopic = salt.utils.stringutils.to_bytes(hashlib.sha1(topic).hexdigest()) pub_sock.send(htopic, flags=zmq.SNDMORE) pub_sock.send(payload) log.trace('Filtered data has been sent') # Syndic broadcast if self.opts.get('order_masters'): log.trace('Sending filtered data to syndic') pub_sock.send(b'syndic', flags=zmq.SNDMORE) pub_sock.send(payload) log.trace('Filtered data has been sent to syndic') # otherwise its a broadcast else: # TODO: constants file for "broadcast" log.trace('Sending broadcasted data over publisher %s', pub_uri) pub_sock.send(b'broadcast', flags=zmq.SNDMORE) pub_sock.send(payload) log.trace('Broadcasted data has been sent') else: log.trace('Sending ZMQ-unfiltered data over publisher %s', pub_uri) pub_sock.send(payload) log.trace('Unfiltered data has been sent') except zmq.ZMQError as exc: if exc.errno == errno.EINTR: continue raise exc except KeyboardInterrupt: # Cleanly close the sockets if we're shutting down if pub_sock.closed is False: pub_sock.setsockopt(zmq.LINGER, 1) pub_sock.close() if pull_sock.closed is False: pull_sock.setsockopt(zmq.LINGER, 1) pull_sock.close() if context.closed is False: context.term()