예제 #1
0
def MultiBuildingAgent(config_path=None, **kwargs):
    '''Return agent object providing multi-building messaging.

    The configuration file, if given by config_path, may contain the
    declarations below. An initial configuration may be passed in as a
    dictionary via the config keyword argument.

      building-publish-address:
          A ØMQ address used to publish to the building's message bus.
          Defaults to 'tcp://0.0.0.0:9161'.

      building-subscribe-address:
          A ØMQ address used to subscribe to the building's message bus.
          Defaults to 'tcp://0.0.0.0:9160'.

      public-key, secret-key:
          Curve keypair (create with zmq.curve_keypair()) to use for
          authentication and encryption. If not provided, all
          communications will be unencrypted.

      cleanup-period:
          Frequency, in seconds, to check for and close stale
          connections. Defaults to 600 seconds (10 minutes).

      hosts:
          A mapping (dictionary) of building names to publish/subscribe
          addresses. Each entry is of the form:

              <BUILDING>: {'pub': <PUB_ADDRESS>, 'sub': <SUB_ADDRESS>,
                           'public-key': <PUBKEY>, 'allow': <PUB_OR_SUB>}

          where <BUILDING>, <PUB_ADDRESS, <SUB_ADDRESS>, <PUBKEY>, and
          <PUB_OR_SUB> are all strings specifying the building name as
          'CAMPUS/BUILDING', the publish and subscribe addresses as
          ØMQ addresses, the curve public key, and either 'pub' or 'sub'
          to allow publish only or both publish and subscribe.

      uuid:
          A UUID to use in the Cookie header. If not given, one will
          be automatically generated.
    '''

    config = kwargs.pop('config', {})
    if config_path:
        config.update(utils.load_config(config_path))
    cleanup_period = config.get('cleanup-period', 600)
    assert cleanup_period >= 1

    class Proxy(PublishMixin, BaseAgent):
        '''Proxy messages between internal bus and other buildings.

        This class could be combined with the Agent class below rather
        than using it as a base class. Keeping the implementations
        separate, however, provides for a cleaner implementation and
        allows one to more easily track the agent logic.
        '''
        def __init__(self, **kwargs):
            '''Create and register the external sockets.'''
            super(Proxy, self).__init__(**kwargs)
            # Use separate context for these sockets to avoid
            # authentication conflicts with other sockets.
            ctx = zmq.Context()
            self.zap_sock = ctx.socket(zmq.REP)
            self.zap_sock.bind('inproc://zeromq.zap.01')
            self.reactor.register(self.zap_sock, self.handle_authentication)
            self.outgoing = messaging.Socket(zmq.XPUB, context=ctx)
            self._config_socket(self.outgoing)
            self.outgoing.zap_domain = 'building.outgoing'
            self.incoming = messaging.Socket(zmq.PULL, context=ctx)
            self._config_socket(self.incoming)
            self.incoming.zap_domain = 'building.incoming'
            key = config.get('secret-key')
            if key:
                self.outgoing.curve_secretkey = key
                self.outgoing.curve_server = 1
                self.incoming.curve_secretkey = key
                self.incoming.curve_server = 1
            self.hosts = config.get('hosts', {})
            print "self.hosts " + str(self.hosts)
            self.allow_sub = set(key for host in self.hosts.itervalues()
                                 for key, allow in [(host.get('public-key'),
                                                     host.get('allow', 'sub'))]
                                 if key and allow in ['pub', 'sub'])
            print "self.allow_sub " + str(self.allow_sub)
            self.allow_pub = set(key for host in self.hosts.itervalues()
                                 for key, allow in [(host.get('public-key'),
                                                     host.get('allow', 'sub'))]
                                 if key and allow == 'pub')
            print "self.allow_pub " + str(self.allow_pub)

        def _config_socket(self, sock):
            sock.reconnect_ivl = 1000
            sock.reconnect_ivl_max = 180000
            sock.sndtimeo = 10000
            sock.rcvtimeo = 10000
            sock.linger = 10000

        def setup(self):
            '''Bind the external ports.'''
            super(Proxy, self).setup()
            self.reactor.register(self.outgoing, self.handle_subscribe)
            self.outgoing.bind(
                config.get('building-subscribe-address', 'tcp://0.0.0.0:9160'))
            pub_addr = config.get('building-publish-address',
                                  'tcp://0.0.0.0:9161')
            print "sub_addr " + config.get('building-subscribe-address')
            print "pub_addr " + pub_addr
            if pub_addr:
                self.reactor.register(self.incoming, self.handle_incoming)
                self.incoming.bind(pub_addr)

        def handle_incoming(self, sock):
            '''Receive incoming messages and publish to internal bus.'''
            try:
                topic, headers, message = self.incoming.recv_message(NOBLOCK)
            except ZMQError as e:
                if e.errno == EINTR:
                    return
                raise
            self.publish(topic, headers, *message)

        def handle_subscribe(self, sock):
            '''Manage external subscription messages.'''
            try:
                message = self.outgoing.recv(NOBLOCK)
            except ZMQError as e:
                if e.errno == EINTR:
                    return
                raise
            if message:
                add = bool(ord(message[0]))
                topic = message[1:]
                if add:
                    self.subscribe(topic, self.on_outgoing)
                else:
                    self.unsubscribe(topic)

        def handle_authentication(self, sock):
            '''Restrict connections to approved clients.'''
            allow = False
            auth = sock.recv_multipart()
            version, sequence, domain, address, identity, mechanism = auth[:6]
            assert version == '1.0'
            if mechanism == 'CURVE':
                creds = zmq.utils.z85.encode(auth[6])
                if domain == 'building.outgoing':
                    allow = creds in self.allow_sub
                elif domain == 'building.incoming':
                    allow = creds in self.allow_pub
            elif mechanism == 'NULL':
                allow, creds = True, ''
            else:
                creds = ''
            _log.info('{} {} at {} via {} {}'.format(
                'allow' if allow else 'deny', address, domain, mechanism,
                creds))
            if allow:
                reply = [version, sequence, "200", "OK", "", ""]
            else:
                reply = [version, sequence, "400", "Forbidden", "", ""]
            sock.send_multipart(reply)

        def on_outgoing(self, topic, headers, message, match):
            '''Forward messages to external subscribers.'''
            while True:
                try:
                    self.outgoing.send_message(topic,
                                               headers,
                                               *message,
                                               flags=NOBLOCK)
                except ZMQError as e:
                    if e.errno == EINTR:
                        continue
                    if e.errno != EAGAIN:
                        raise
                break

    class Agent(Proxy):
        '''Provide inter-building publish/subscribe service.

        Provides three topics for inter-building messaging:

          building/recv/<CAMPUS>/<BUILDING>/<TOPIC>:
              Agents can subscribe to this topic to receive messages
              sent to <TOPIC> ant the building specified by
              <CAMPUS>/<BUILDING>.

          building/send/<CAMPUS>/<BUILDING>/<TOPIC>:
              Agents can send messages to this topic to have them
              forwarded to <TOPIC> at the building specified by
              <CAMPUS>/<BUILDING>.
            
          building/error/<CAMPUS>/<BUILDING>/<TOPIC>
              Errors encountered during sending/receiving to/from the
              above two topics will be sent over this topic.
        '''
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self.uuid = config.get('uuid') or str(uuid.uuid4())
            self.subs = {}
            self.rsubs = {}
            self.pubs = {}
            self.sequence = 0

        def setup(self):
            '''Request list of current subscriptions.'''
            super(Agent, self).setup()
            # Check and connect existing subscriptions
            self.publish('subscriptions/list/building/recv/',
                         {'Cookie': 'init ' + self.uuid})

        @matching.match_regex(
            'subscriptions/add/building/recv/([^/]+/[^/]+)/(.*)')
        def on_subscribe(self, full_topic, headers, message, match):
            print "full_topic " + str(full_topic)
            print "headers " + str(headers)
            print "message " + str(message)
            '''Handle new external building subscription requests.'''
            building, topic = match.groups()
            self.add_subscription(building,
                                  topic,
                                  cookie=headers.get('Cookie'))

        def add_subscription(self, building, topic, cookie=None):
            '''Add external building subscription.'''
            sock = self.subs.get(building)
            _launch_file = os.path.join(
                Agents_DIR + "MultiBuilding/multibuildingagent.launch.json")
            with open(_launch_file, 'r') as f:
                self.multi_node_data = json.load(f)
            self.hosts = self.multi_node_data['hosts']
            if not sock:
                host = self.hosts.get(building)
                address = host.get('sub') if host else None
                print "address" + str(address)
                # Handle missing address
                if not address:
                    headers = {'Cookie': cookie} if cookie else {}
                    self.publish_error(
                        building, topic, headers, errno.ENOENT,
                        'building subscription address not found')
                    return
                sock = messaging.Socket(zmq.SUB)
                key = host.get('public-key')
                if key:
                    sock.curve_serverkey = key
                    sock.curve_secretkey = config.get('secret-key')
                    sock.curve_publickey = config.get('public-key')
                self._config_socket(sock)
                sock.connect(address)
                self.subs[building] = sock
                self.rsubs[sock] = building
                self.reactor.register(sock, self.handle_republish)
            sock.subscribe = topic.encode('utf-8')

        def handle_republish(self, sock):
            '''Publish incoming messages on internal bus.'''
            print "handle_republish sock " + str(sock)
            building = self.rsubs[sock]
            try:
                orig_topic, headers, message = sock.recv_message(flags=NOBLOCK)
            except ZMQError as e:
                if e.errno == EINTR:
                    return
                self.reactor.unregister(sock)
                self.subs.pop(building, None)
                self.rsubs.pop(sock, None)
                sock.close()
                return
            topic = 'building/recv/{}/{}'.format(building, orig_topic)
            self.publish(topic, headers, *message)

        def publish_error(self, building, topic, headers, errnum, message):
            '''Publish errors to error topic.'''
            topic = 'building/error/{}/{}'.format(building, topic)
            self.publish(topic, headers, str(errnum), message)
            print "DONE publish error"

        @matching.match_regex(
            'subscriptions/remove/building/recv/([^/]+/[^/]+)/(.*)')
        def on_unsubscribe(self, full_topic, headers, message, match):
            '''Handle external building unsubscribe requests.'''
            building, topic = match.groups()
            sock = self.subs.get(building)
            if sock:
                sock.unsubscribe = topic

        @matching.match_regex('building/send/([^/]+/[^/]+)/(.*)')
        def on_send(self, full_topic, headers, message, match):
            '''Handle external building publish requests.'''
            building, topic = match.groups()
            _launch_file = os.path.join(
                Agents_DIR + "MultiBuilding/multibuildingagent.launch.json")
            with open(_launch_file, 'r') as f:
                self.multi_node_data = json.load(f)
            # self.connected_nodes = list()
            # for k, v in self.multi_node_data['hosts'].items():
            self.hosts = self.multi_node_data['hosts']
            # pass
            # else:
            # self.connected_nodes.append(k)
            print "MultiBuilding Agent sending message to building: " + building
            print "MultiBuilding Agent sending message topic: " + topic
            sock, seq = self.pubs.get(building, (None, None))
            # print "sock "+str(sock)
            # print "seq "+str(seq)
            if not sock:
                host = self.hosts.get(building)
                address = host.get('pub') if host else None
                print "host " + str(host)
                print "address " + str(address)
                # Handle missing address
                if not address:
                    cookie = headers.get('Cookie')
                    headers = {'Cookie': cookie} if cookie else {}
                    self.publish_error(building, topic, headers, errno.ENOENT,
                                       'building publish address not found')
                    return
                sock = messaging.Socket(zmq.PUSH)
                key = host.get('public-key')
                if key:
                    sock.curve_serverkey = key
                    sock.curve_secretkey = config.get('secret-key')
                    sock.curve_publickey = config.get('public-key')
                self._config_socket(sock)
                sock.connect(address)
            if seq != self.sequence:
                self.pubs[building] = sock, self.sequence
            while True:
                try:
                    sock.send_message(topic, headers, *message, flags=NOBLOCK)
                    print "MultiBuiding Agent sent message"
                except ZMQError as e:
                    if e.errno == EINTR:
                        continue
                    self.pubs.pop(building, None)
                    sock.close()
                    self.publish_error(building, topic, headers,
                                       errno.ECONNABORTED,
                                       'message not sent; socket closed')
                break

        @periodic(cleanup_period)
        def on_cleanup(self):
            '''Periodically request subscription list for cleaning.'''
            self.publish('subscriptions/list/building/recv/',
                         {'Cookie': 'clean ' + self.uuid})
            for building, (sock, seq) in self.pubs.items():
                if seq != self.sequence:
                    del self.pubs[building]
            self.sequence += 1

        @matching.match_exact('subscriptions/list/building/recv/')
        def on_subscription_list(self, topic, headers, message, match):
            '''Handle closing unused sockets.'''
            # print "on_subscription_init "+str(topic)
            # print "on_subscription_init "+str(headers)
            # print "on_subscription_init "+str(message)
            if headers.get('Cookie') != 'clean ' + self.uuid:
                return
            topics = set()
            for prefix in message:
                try:
                    campus, building = prefix[33:].split('/', 2)[:2]
                except ValueError:
                    continue
                topics.add('/'.join([campus, building]))
            for building, sock in self.subs.values():
                if building not in topics:
                    self.subs.pop(building, None)
                    self.rsubs.pop(sock, None)
                    try:
                        self.reactor.unregister(sock)
                    except KeyError:
                        pass
                    sock.close()

        @matching.match_exact('subscriptions/list/building/recv/')
        def on_subscription_init(self, topic, headers, message, match):
            '''Handle existing subscriptions to external buildings on start.'''
            # print "on_subscription_init "+str(topic)
            # print "on_subscription_init "+str(headers)
            # print "on_subscription_init "+str(message)
            if headers.get('Cookie') != 'init ' + self.uuid:
                return
            for prefix in message:
                try:
                    ## len('building/recv/') == 14
                    campus, building, topic = prefix[14:].split('/', 2)
                except ValueError:
                    continue
                building = '/'.join([campus, building])
                self.add_subscription(building, topic)

    # Rename agent to match factory function.
    Agent.__name__ = 'MultiBuildingAgent'
    return Agent(**kwargs)
예제 #2
0
def MultiBuildingAgent(config_path=None, **kwargs):
    '''Return agent object providing multi-building messaging.

    The configuration file, if given by config_path, may contain the
    declarations below. An initial configuration may be passed in as a
    dictionary via the config keyword argument.

      building-publish-address:
          A ØMQ address used to publish to the building's message bus.
          Defaults to 'tcp://0.0.0.0:9161'.

      building-subscribe-address:
          A ØMQ address used to subscribe to the building's message bus.
          Defaults to 'tcp://0.0.0.0:9160'.

      public-key, secret-key:
          Curve keypair (create with zmq.curve_keypair()) to use for
          authentication and encryption. If not provided, all
          communications will be unencrypted.

      cleanup-period:
          Frequency, in seconds, to check for and close stale
          connections. Defaults to 600 seconds (10 minutes).

      hosts:
          A mapping (dictionary) of building names to publish/subscribe
          addresses. Each entry is of the form:

              <BUILDING>: {'pub': <PUB_ADDRESS>, 'sub': <SUB_ADDRESS>,
                           'public-key': <PUBKEY>, 'allow': <PUB_OR_SUB>}

          where <BUILDING>, <PUB_ADDRESS, <SUB_ADDRESS>, <PUBKEY>, and
          <PUB_OR_SUB> are all strings specifying the building name as
          'CAMPUS/BUILDING', the publish and subscribe addresses as
          ØMQ addresses, the curve public key, and either 'pub' or 'sub'
          to allow publish only or both publish and subscribe.

      uuid:
          A UUID to use in the Cookie header. If not given, one will
          be automatically generated.
    '''

    config = kwargs.pop('config', {})
    if config_path:
        config.update(utils.load_config(config_path))
    cleanup_period = config.get('cleanup-period', 600)
    assert cleanup_period >= 1

    class Proxy(PublishMixin, BaseAgent):
        '''Proxy messages between internal bus and other buildings.

        This class could be combined with the Agent class below rather
        than using it as a base class. Keeping the implementations
        separate, however, provides for a cleaner implementation and
        allows one to more easily track the agent logic.
        '''

        def __init__(self, **kwargs):
            '''Create and register the external sockets.'''
            super(Proxy, self).__init__(**kwargs)
            # Use separate context for these sockets to avoid
            # authentication conflicts with other sockets.
            ctx = zmq.Context()
            self.zap_sock = ctx.socket(zmq.REP)
            self.zap_sock.bind('inproc://zeromq.zap.01')
            self.reactor.register(self.zap_sock, self.handle_authentication)
            self.outgoing = messaging.Socket(zmq.XPUB, context=ctx)
            self._config_socket(self.outgoing)
            self.outgoing.zap_domain = 'building.outgoing'
            self.incoming = messaging.Socket(zmq.PULL, context=ctx)
            self._config_socket(self.incoming)
            self.incoming.zap_domain = 'building.incoming'
            key = config.get('secret-key')
            if key:
                self.outgoing.curve_secretkey = key
                self.outgoing.curve_server = 1
                self.incoming.curve_secretkey = key
                self.incoming.curve_server = 1
            self.hosts = config.get('hosts', {})
            print "self.hosts "+str(self.hosts)
            self.allow_sub = set(key for host in self.hosts.itervalues()
                    for key, allow in [(host.get('public-key'), host.get('allow', 'sub'))]
                        if key and allow in ['pub', 'sub'])
            print "self.allow_sub "+str(self.allow_sub)
            self.allow_pub = set(key for host in self.hosts.itervalues()
                    for key, allow in [(host.get('public-key'), host.get('allow', 'sub'))]
                        if key and allow == 'pub')
            print "self.allow_pub "+str(self.allow_pub)

        def _config_socket(self, sock):
            sock.reconnect_ivl = 1000
            sock.reconnect_ivl_max = 180000
            sock.sndtimeo = 10000
            sock.rcvtimeo = 10000
            sock.linger = 10000

        def setup(self):
            '''Bind the external ports.'''
            super(Proxy, self).setup()
            self.reactor.register(self.outgoing, self.handle_subscribe)
            self.outgoing.bind(config.get(
                    'building-subscribe-address', 'tcp://0.0.0.0:9160'))
            pub_addr = config.get('building-publish-address',
                                  'tcp://0.0.0.0:9161')
            print "sub_addr "+config.get('building-subscribe-address')
            print "pub_addr "+pub_addr
            if pub_addr:
                self.reactor.register(self.incoming, self.handle_incoming)
                self.incoming.bind(pub_addr)

        def handle_incoming(self, sock):
            '''Receive incoming messages and publish to internal bus.'''
            try:
                topic, headers, message = self.incoming.recv_message(NOBLOCK)
            except ZMQError as e:
                if e.errno == EINTR:
                    return
                raise
            self.publish(topic, headers, *message)

        def handle_subscribe(self, sock):
            '''Manage external subscription messages.'''
            try:
                message = self.outgoing.recv(NOBLOCK)
            except ZMQError as e:
                if e.errno == EINTR:
                    return
                raise
            if message:
                add = bool(ord(message[0]))
                topic = message[1:]
                if add:
                    self.subscribe(topic, self.on_outgoing)
                else:
                    self.unsubscribe(topic)

        def handle_authentication(self, sock):
            '''Restrict connections to approved clients.'''
            allow = False
            auth = sock.recv_multipart()
            version, sequence, domain, address, identity, mechanism = auth[:6]
            assert version == '1.0'
            if mechanism == 'CURVE':
                creds = zmq.utils.z85.encode(auth[6])
                if domain == 'building.outgoing':
                    allow = creds in self.allow_sub
                elif domain == 'building.incoming':
                    allow = creds in self.allow_pub
            elif mechanism == 'NULL':
                allow, creds = True, ''
            else:
                creds = ''
            _log.info('{} {} at {} via {} {}'.format(
                    'allow' if allow else 'deny', address, domain,
                    mechanism, creds))
            if allow:
                reply = [version, sequence, "200", "OK", "", ""]
            else:
                reply = [version, sequence, "400", "Forbidden", "", ""]
            sock.send_multipart(reply)

        def on_outgoing(self, topic, headers, message, match):
            '''Forward messages to external subscribers.'''
            while True:
                try:
                    self.outgoing.send_message(
                            topic, headers, *message, flags=NOBLOCK)
                except ZMQError as e:
                    if e.errno == EINTR:
                        continue
                    if e.errno != EAGAIN:
                        raise
                break

    class Agent(Proxy):
        '''Provide inter-building publish/subscribe service.

        Provides three topics for inter-building messaging:

          building/recv/<CAMPUS>/<BUILDING>/<TOPIC>:
              Agents can subscribe to this topic to receive messages
              sent to <TOPIC> ant the building specified by
              <CAMPUS>/<BUILDING>.

          building/send/<CAMPUS>/<BUILDING>/<TOPIC>:
              Agents can send messages to this topic to have them
              forwarded to <TOPIC> at the building specified by
              <CAMPUS>/<BUILDING>.
            
          building/error/<CAMPUS>/<BUILDING>/<TOPIC>
              Errors encountered during sending/receiving to/from the
              above two topics will be sent over this topic.
        '''

        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self.uuid = config.get('uuid') or str(uuid.uuid4())
            self.subs = {}
            self.rsubs = {}
            self.pubs = {}
            self.sequence = 0

        def setup(self):
            '''Request list of current subscriptions.'''
            super(Agent, self).setup()
            # Check and connect existing subscriptions
            self.publish('subscriptions/list/building/recv/',
                         {'Cookie': 'init ' + self.uuid})

        @matching.match_regex('subscriptions/add/building/recv/([^/]+/[^/]+)/(.*)')
        def on_subscribe(self, full_topic, headers, message, match):
            print "full_topic "+str(full_topic)
            print "headers "+str(headers)
            print "message "+str(message)
            '''Handle new external building subscription requests.'''
            building, topic = match.groups()
            self.add_subscription(building, topic, cookie=headers.get('Cookie'))

        def add_subscription(self, building, topic, cookie=None):
            '''Add external building subscription.'''
            sock = self.subs.get(building)
            _launch_file = os.path.join(Agents_DIR+"MultiBuilding/multibuildingagent.launch.json")
            with open(_launch_file, 'r') as f:
                self.multi_node_data = json.load(f)
            self.hosts = self.multi_node_data['hosts']
            if not sock:
                host = self.hosts.get(building)
                address = host.get('sub') if host else None
                print "address"+str(address)
                # Handle missing address
                if not address:
                    headers = {'Cookie': cookie} if cookie else {}
                    self.publish_error(building, topic, headers, errno.ENOENT,
                                       'building subscription address not found')
                    return
                sock = messaging.Socket(zmq.SUB)
                key = host.get('public-key')
                if key:
                    sock.curve_serverkey = key
                    sock.curve_secretkey = config.get('secret-key')
                    sock.curve_publickey = config.get('public-key')
                self._config_socket(sock)
                sock.connect(address)
                self.subs[building] = sock
                self.rsubs[sock] = building
                self.reactor.register(sock, self.handle_republish)
            sock.subscribe = topic.encode('utf-8')

        def handle_republish(self, sock):
            '''Publish incoming messages on internal bus.'''
            print "handle_republish sock "+str(sock)
            building = self.rsubs[sock]
            try:
                orig_topic, headers, message = sock.recv_message(flags=NOBLOCK)
            except ZMQError as e:
                if e.errno == EINTR:
                    return
                self.reactor.unregister(sock)
                self.subs.pop(building, None)
                self.rsubs.pop(sock, None)
                sock.close()
                return
            topic = 'building/recv/{}/{}'.format(building, orig_topic)
            self.publish(topic, headers, *message)

        def publish_error(self, building, topic, headers, errnum, message):
            '''Publish errors to error topic.'''
            topic = 'building/error/{}/{}'.format(building, topic)
            self.publish(topic, headers, str(errnum), message)
            print "DONE publish error"

        @matching.match_regex('subscriptions/remove/building/recv/([^/]+/[^/]+)/(.*)')
        def on_unsubscribe(self, full_topic, headers, message, match):
            '''Handle external building unsubscribe requests.'''
            building, topic = match.groups()
            sock = self.subs.get(building)
            if sock:
                sock.unsubscribe = topic

        @matching.match_regex('building/send/([^/]+/[^/]+)/(.*)')
        def on_send(self, full_topic, headers, message, match):
            '''Handle external building publish requests.'''
            building, topic = match.groups()
            _launch_file = os.path.join(Agents_DIR+"MultiBuilding/multibuildingagent.launch.json")
            with open(_launch_file, 'r') as f:
                self.multi_node_data = json.load(f)
            # self.connected_nodes = list()
            # for k, v in self.multi_node_data['hosts'].items():
            self.hosts = self.multi_node_data['hosts']
                    # pass
                # else:
                # self.connected_nodes.append(k)
            print "MultiBuilding Agent sending message to building: "+building
            print "MultiBuilding Agent sending message topic: "+topic
            sock, seq = self.pubs.get(building, (None, None))
            # print "sock "+str(sock)
            # print "seq "+str(seq)
            if not sock:
                host = self.hosts.get(building)
                address = host.get('pub') if host else None
                print "host "+str(host)
                print "address "+str(address)
                # Handle missing address
                if not address:
                    cookie = headers.get('Cookie')
                    headers = {'Cookie': cookie} if cookie else {}
                    self.publish_error(building, topic, headers, errno.ENOENT,
                                       'building publish address not found')
                    return
                sock = messaging.Socket(zmq.PUSH)
                key = host.get('public-key')
                if key:
                    sock.curve_serverkey = key
                    sock.curve_secretkey = config.get('secret-key')
                    sock.curve_publickey = config.get('public-key')
                self._config_socket(sock)
                sock.connect(address)
            if seq != self.sequence:
                self.pubs[building] = sock, self.sequence
            while True:
                try:
                    sock.send_message(topic, headers, *message, flags=NOBLOCK)
                    print "MultiBuiding Agent sent message"
                except ZMQError as e:
                    if e.errno == EINTR:
                        continue
                    self.pubs.pop(building, None)
                    sock.close()
                    self.publish_error(building, topic, headers,
                                       errno.ECONNABORTED,
                                       'message not sent; socket closed')
                break
                

        @periodic(cleanup_period)
        def on_cleanup(self):
            '''Periodically request subscription list for cleaning.'''
            self.publish('subscriptions/list/building/recv/',
                         {'Cookie': 'clean ' + self.uuid})
            for building, (sock, seq) in self.pubs.items():
                if seq != self.sequence:
                    del self.pubs[building]
            self.sequence += 1

        @matching.match_exact('subscriptions/list/building/recv/')
        def on_subscription_list(self, topic, headers, message, match):
            '''Handle closing unused sockets.'''
            # print "on_subscription_init "+str(topic)
            # print "on_subscription_init "+str(headers)
            # print "on_subscription_init "+str(message)
            if headers.get('Cookie') != 'clean ' + self.uuid:
                return
            topics = set()
            for prefix in message:
                try:
                    campus, building = prefix[33:].split('/', 2)[:2]
                except ValueError:
                    continue
                topics.add('/'.join([campus, building]))
            for building, sock in self.subs.values():
                if building not in topics:
                    self.subs.pop(building, None)
                    self.rsubs.pop(sock, None)
                    try:
                        self.reactor.unregister(sock)
                    except KeyError:
                        pass
                    sock.close()

        @matching.match_exact('subscriptions/list/building/recv/')
        def on_subscription_init(self, topic, headers, message, match):
            '''Handle existing subscriptions to external buildings on start.'''
            # print "on_subscription_init "+str(topic)
            # print "on_subscription_init "+str(headers)
            # print "on_subscription_init "+str(message)
            if headers.get('Cookie') != 'init ' + self.uuid:
                return
            for prefix in message:
                try:
                    ## len('building/recv/') == 14
                    campus, building, topic = prefix[14:].split('/', 2)
                except ValueError:
                    continue
                building = '/'.join([campus, building])
                self.add_subscription(building, topic)

    # Rename agent to match factory function.
    Agent.__name__ = 'MultiBuildingAgent'
    return Agent(**kwargs)