def _create_connection(self): """Create our 'zk' connection object. If the object does not exist, create it. If it does exist, move on. """ if self._zk: return # Define our zookeeper client here so that it never gets overwritten self._zk = ZookeeperClient(hosts=self._server, timeout=self._timeout, read_only=self._readonly, handler=EventHandler(), retry_delay=0.1, retry_backoff=2, retry_max_delay=10) # Set up our rate limiting self._zk.set_rate_limiter(delay=self._rate_limit_time, calls=self._rate_limit_calls) # Get a lock handler self._run_lock = self._zk.handler.lock_object() # Watch for any connection state changes self._zk.add_listener(self._state_listener)
class KazooServiceRegistry(nd_service_registry): _instance = None _initialized = False def __new__(self, **kwargs): """Only creates a new object if one does not already exist.""" if self._instance is not None: return self._instance self._instance = object.__new__(self) return self._instance def __init__(self, server=SERVER, readonly=False, timeout=TIMEOUT, cachefile=None, username=None, password=None, acl=None, lazy=False): """Initialize the KazooServiceRegistry object. Generally speaking, you can initialize the object with no explicit settings and it will work fine as long as your Zookeeper server is available at 'localhost:2181'. A few notes though about some of the options... 'lazy' mode allows the object to initialize and lazily connect to the Zookeeper services. If Zookeeper is down, it will continue to try to connect in the background, while allowing the object to respond to certain queries out of the 'cachefile'. This option is not very useful without the 'cachefile' setting. 'cachefile' is a location to save a pickle-dump of our various data objects. In the event that we start up in 'lazy' mode and are unable to reach the backend Zookeeper service, objects are re-created with this cache file instead and we are able to respond to get() requests with the latest saved data. Of important note here, if you have multiple processes using the same file location, they will not overwrite, but rather they will append to the file as they save data. Each object saves to this file immediately upon receiving a data-change from the objects... so its generally very up- to-date. 'username' and 'password's can be supplied if you wish to authenticate with Zookeeper by creating a 'Digest Auth'. 'readonly' mode just safely sets our connection status to readonly to prevent any accidental data changes. Good idea for your data consumers. Args: server: String in the format of 'localhost:2181' readonly: Boolean that sets whether our connection to Zookeeper is a read_only connection or not. timeout: Value in seconds for connection timeout lazy: Boolean whether or not to allow lazy connection mode cachefile: Location of desired cachefile username: Username to create Digest Auth with password: Password to create Digest Auth with acl: A Kazoo ACL-object if special ACLs are desired """ # See if we're already initialized. If we are, just break out quickly. if self._initialized: return # Create our logger self.log = logging.getLogger('%s.KazooServiceRegistry' % __name__) self.log.info('Initializing ServiceRegistry object') # Quiet down the Kazoo connection ping messages logging.getLogger('kazoo.protocol.connection').addFilter(shims.KazooFilter()) # Record the supplied settings self._timeout = timeout self._username = username self._password = password self._readonly = readonly self._acl = acl self._server = server self._lazy = lazy self._pid = os.getpid() self._zk = None # Store all of our Registration objects here self._registrations = {} # Store all of our Watcher objects here self._watchers = {} # Create a local 'dict' that we'll use to store the results of our # get_nodes/get_node_data calls. self._cachefile = cachefile # up things like authentication) self._connect(lazy) # On python interpreter shutdown, cleanly disconnect our connections atexit.register(self._shutdown) # Mark us as initialized self._initialized = True self.log.info('Initialization Done!') def _shutdown(self): """Cleanly shut down our connections.""" self.log.debug('Shutting down...') # Quiet down the loggers to only show major errors during the shutdown # process. self.log.setLevel(logging.ERROR) logging.getLogger('kazoo').setLevel(logging.ERROR) logging.getLogger('kazoo.protocol.connection').setLevel(logging.ERROR) # Now disconnect our connection self.stop() def _health_check(func): """Decorator used to heathcheck the Zookeeper connection. If this healthcheck fails, we raise a ServiceUnavailable exception. If we detect that we've been forked, then we re-create our connection to the Zookeeper backend and move on with our health check.""" @wraps(func) def _health_check_decorator(self, *args, **kwargs): self.log.debug('Running healthcheck...') pid = os.getpid() if pid != self._pid: self.log.warning('Fork detected!') self._pid = pid self.rebuild() # check if our connection is up or not if not self._zk.connected: e = 'Service is down. Try again later.' raise exceptions.NoConnection(e) return func(self, *args, **kwargs) return _health_check_decorator def stop(self): """Cleanly stops our Zookeeper connection. All Registration/Watch objects stick around. If the connection is re-established, they will automatically re-create their connections.""" try: self._zk.stop() except: pass def start(self): """Starts up our ZK connection again. All existing watches/registrations/etc will be re-established.""" self._connect(self._lazy) @_health_check def set(self, node, data, state, type): """Registers a supplied node (full path and nodename). Registers the supplied node-name with Zookeeper and converts the supplied data into JSON-text. Args: node: A string representing the node name and service port (/services/foo/host:port) data: A dict with any additional data you wish to register. state: True/False whether or not the node is actively listed in Zookeeper type: A Registration class object representing the type of node we're registering. Returns: True: registration was successful""" # Check if we're in read-only mode if self._readonly: raise exceptions.ReadOnly('In read-only mode, no writes allowed') # Check if the node is already there or not. If it is, we have to # figure out if we were the ones who registered it or not. If we are, # we leave it alone. If not, we attempt to delete it and register our # own. If we cannot do that, we throw an error. self.log.debug('Looking for Registration object for [%s] with [%s].' % (node, data)) if node in self._registrations: # The Registration objects can stop themselves in the event of # a failure. If they do, lets throw a message, toss the object, # and then let a new one be created. self.log.debug('[%s] already has Registration object.' % node) self._registrations[node].update(data=data, state=state) return True # Create a new registration object self.log.debug('Creating Registration object for [%s]' % node) self._registrations[node] = type(zk=self._zk, path=node, data=data, state=state) return True def unset(self, node): """Destroys a particular Registration object. Args: node: (String) representing the node path to the object""" try: self._registrations[node].stop() except: self.log.warning('Node object for %s not found.' % node) return self.log.info('Registration for %s stopped.' % node) def _create_connection(self): """Create our 'zk' connection object. If the object does not exist, create it. If it does exist, just move on. """ if self._zk: return # Define our zookeeper client here so that it never gets overwritten self._zk = ZookeeperClient(hosts=self._server, timeout=self._timeout, read_only=self._readonly, handler=EventHandler(), retry_delay=0.1, retry_backoff=2, retry_max_delay=10) # Get a lock handler self._run_lock = self._zk.handler.lock_object() # Watch for any connection state changes self._zk.add_listener(self._state_listener) def _connect(self, lazy): """Connect to Zookeeper. This function starts the connection to Zookeeper using the Kazoo start_async() function. By using start_async(), we can continue to re-try the connection if it fails either on the initial __init__ of the module, or if it fails after the object has been running for a while. Args: lazy: True/False - determines whether or not we continue to try to connect in the background if the initial connection fails. """ self.log.info('Connecting to Zookeeper Service (%s)' % self._server) # Create our zookeeper connection object self._create_connection() # Start our connection asynchronously self.event = self._zk.start_async() self.event.wait(timeout=self._timeout) # After the timeout above, check if we're connected. if not self._zk.connected: # If we allow lazy-connection mode, we'll continue to try to # connect in the background. In the foreground, we'll check if # theres a locally cached dict() file that we can grab some data # from so that we can function partially. if lazy: self.log.warning( 'Could not reach Zookeeper server. ' 'Starting up in crippled mode. ' 'Will continue to try to connect in the background.') else: # If lazy mode is False, then we stop trying to connect to # Zookeeper and raise an exception. The client can deal with # what-to-do at this point. self._zk.stop() raise exceptions.NoConnection( 'Could not connect to Zookeeper') @_health_check def _setup_auth(self): """Set up our credentials with the Zookeeper service. If credentials were passwed to us, authenticate with Zookeeper. These credentials do not have to exist in the system, they're compared against other credentials to validate whether two users are the same, or whether a particular set of credentials has access to a particular node. """ with self._run_lock: if self._username and self._password: # If an ACL was provided, we'll use it. If not though, we'll # create our own ACL described below: # # This ACL essentially allows our USERNAME+PASSWORD combo to # completely own any nodes that were also created with the same # USERNAME+PASSWORD combo. This means that if all of your # production machines share a particular username/password, # they can each mess with the other machines node # registrations. # # Its highly recommended that you break up your server farms # into different permission groups. ACL = kazoo.security.make_digest_acl(self._username, self._password, all=True) # This allows *all* users to read child nodes, but disallows # them from reading, updating permissions, deleting child # nodes, or writing to child nodes that they do not own. READONLY_ACL = kazoo.security.make_acl('world', 'anyone', create=False, delete=False, write=False, read=True, admin=False) self.log.debug('Credentials were supplied, adding auth.') self._zk.retry(self._zk.add_auth, 'digest', "%s:%s" % (self._username, self._password)) if not self._acl: self._acl = (ACL, READONLY_ACL) # If an ACL was providfed, or we dynamically generated one with the # username/password, then set it. if self._acl: self._zk.default_acl = (ACL, READONLY_ACL) def _state_listener(self, state): """Listens for state changes about our connection. If our client becomes disconnected, we set a local variable that lets the rest of the code know to not try to run any Zookeeper commands until the service is back up.""" message='Zookeeper connection state changed' if state == KazooState.SUSPENDED: # In this state, just mark that we can't handle any 'writes' right # now but that we might come back to life soon. self.log.warning('%s: %s' % (message,state)) return elif state == KazooState.LOST: # If we enter the LOST state, we've started a whole new session # with the Zookeeper server. Watches are re-established auto- # magically. Registered paths are re-established by their own # Registration control objects. self.log.warning('%s: %s' % (message,state)) return else: self.log.info('%s: %s' % (message,state)) # We've re-connected, so re-configure our auth digest settings self._setup_auth() # We are not allowed to call any blocking calls in this callback # because it is actually triggered by the thread that holds onto # the Zookeeper connection -- and that causes a deadlock. # # In order to re-register our Watchers, we use the Kazoo spawn() # function. This runs the function in a separate thread and allows # the state_listener function to return quickly. self._zk.handler.spawn(self._convert_dummywatchers_to_watchers) def add_callback(self, path, callback): """Adds a callback in the event of a path change. Adds a callback to a given watcher. If the watcher doesnt exist, we create it with that callback. Either way, your callback is immediately executed with the service data. Args: path: A string reprsenting the path to watch for changes. callback: Reference to the function to callback to. """ if path in self._watchers: self.log.debug('Found [%s] in watchers. Adding callback.' % path) self._watchers[path].add_callback(callback) else: self.log.debug('No existing watcher for [%s] exists. Creating.' % path) self.get(path, callback=callback) def get(self, path, callback=None): """Retrieves a list of nodes (or a single node) in dict() form. Creates a Watcher object for the supplied path and returns the data for the requested path. Triggers callback immediately. Args: path: A string representing the path to the servers. callback: (optional) reference to function to call if the path changes. Returns: nd_service_registry.Watcher.get() dict object """ # Return the object from our cache, if it's there self.log.debug('[%s] Checking for existing object...' % path) if path in self._watchers: self.log.debug('Found [%s] in cache: %s' % (path, str(self._watchers[path].get()))) # If a callback was suplied, but we already have a Watcher object, # add that callback to the existing object. if callback: self._watchers[path].add_callback(callback) # Return the Watcher object get() data. return self._watchers[path].get() try: # Go get a Watcher object since one doesnt already exist self._watchers[path] = self._get_watcher(path, callback) return self._watchers[path].get() except exceptions.NoConnection, e: # Get a DummyWatcher cached object instead self.log.warning('Health Check failed: %s' % e) try: self.log.info('[%s] Loading from cache instead' % path) self._watchers[path] = self._get_dummywatcher(path, callback) return self._watchers[path].get() except exceptions.ServiceRegistryException, e: # Ugh. Total failure. Return false self.log.error('Unable to retrieve [%s] from Zookeeper or cache - ' 'try again later: %s' % (path, e))
class KazooServiceRegistry(nd_service_registry): _instance = None _initialized = False def __new__(self, **kwargs): """Only creates a new object if one does not already exist.""" if self._instance is not None: return self._instance self._instance = object.__new__(self) return self._instance def __init__(self, server=SERVER, readonly=False, timeout=TIMEOUT, cachefile=None, username=None, password=None, acl=None, lazy=False, rate_limit_time=2, rate_limit_calls=50): """Initialize the KazooServiceRegistry object. Generally speaking, you can initialize the object with no explicit settings and it will work fine as long as your Zookeeper server is available at 'localhost:2181'. A few notes though about some of the options... 'lazy' mode allows the object to initialize and lazily connect to the Zookeeper services. If Zookeeper is down, it will continue to try to connect in the background, while allowing the object to respond to certain queries out of the 'cachefile'. This option is not very useful without the 'cachefile' setting. 'cachefile' is a location to save a pickle-dump of our various data objects. In the event that we start up in 'lazy' mode and are unable to reach the backend Zookeeper service, objects are re-created with this cache file instead and we are able to respond to get() requests with the latest saved data. Of important note here, if you have multiple processes using the same file location, they will not overwrite, but rather they will append to the file as they save data. Each object saves to this file immediately upon receiving a data-change from the objects... so its generally very up- to-date. 'username' and 'password's can be supplied if you wish to authenticate with Zookeeper by creating a 'Digest Auth'. 'readonly' mode just safely sets our connection status to readonly to prevent any accidental data changes. Good idea for your data consumers. 'rate_limit_calls' indicates the number of calls to the Zookeeper API that we will allow to pass through unimpeded before we consider whether or not to rate limit them for safety. If None, then this is disabled. 'rate_limit_time' is the average time in seconds you would like to try to maintain between Zookeeper calls. This is only used if 'rate_limit_calls' above is set. Args: server: String in the format of 'localhost:2181' readonly: Boolean that sets whether our connection to Zookeeper is a read_only connection or not. timeout: Value in seconds for connection timeout lazy: Boolean whether or not to allow lazy connection mode cachefile: Location of desired cachefile username: Username to create Digest Auth with password: Password to create Digest Auth with acl: A Kazoo ACL-object if special ACLs are desired rate_limit_time: Target 'sleep' time between Zookeeper API calls. (integer) rate_limit_calls: Minimum number of calls before we begin throttling API calls. (integer) """ # See if we're already initialized. If we are, just break out quickly. if self._initialized: return # Create our logger log.info('Initializing ServiceRegistry object') # Kazoo is very noisy by default. We quiet it down and only pay # attention to the most important messages. logging.getLogger('kazoo').setLevel(logging.WARNING) logging.getLogger('kazoo.protocol.connection').addFilter( shims.KazooFilter()) # Record the supplied settings self._timeout = timeout self._username = username self._password = password self._readonly = readonly self._acl = acl self._server = server self._lazy = lazy self._rate_limit_time = rate_limit_time self._rate_limit_calls = rate_limit_calls self._pid = os.getpid() self._zk = None # Store our connection state, and a list of methods to notify # if that state changes at all. self._conn_state = False self._conn_state_callbacks = [] # Store all of our Registration objects here self._registrations = {} # Store all of our Watcher objects here self._watchers = {} # Store our Lock objects here self._locks = {} # Create a local 'dict' that we'll use to store the results of our # get_nodes/get_node_data calls. self._cachefile = cachefile # up things like authentication) self._connect(lazy) # On python interpreter shutdown, cleanly disconnect our connections atexit.register(self._shutdown) # Mark us as initialized self._initialized = True log.info('Initialization Done!') def _shutdown(self): """Cleanly shut down our connections.""" log.debug('Shutting down...') # Quiet down the loggers to only show major errors during the shutdown # process. log.setLevel(logging.ERROR) logging.getLogger('kazoo').setLevel(logging.ERROR) logging.getLogger('kazoo.protocol.connection').setLevel(logging.ERROR) # Now disconnect our connection self.stop() def _health_check(func): """Decorator used to heathcheck the Zookeeper connection. If this healthcheck fails, we raise a ServiceUnavailable exception. If we detect that we've been forked, then we re-create our connection to the Zookeeper backend and move on with our health check.""" @wraps(func) def _health_check_decorator(self, *args, **kwargs): log.debug('Running healthcheck...') pid = os.getpid() if pid != self._pid: log.info('Fork detected!') self._pid = pid self.rebuild() # check if our connection is up or not if not self._zk.connected: e = 'Service is down. Try again later.' raise exceptions.NoConnection(e) return func(self, *args, **kwargs) return _health_check_decorator def stop(self): """Cleanly stops our Zookeeper connection. All Registration/Watch objects stick around. If the connection is re-established, they will automatically re-create their connections.""" try: self._zk.stop() log.debug('Stopping Zookeeper connection...') except: pass def start(self): """Starts up our ZK connection again. All existing watches/registrations/etc will be re-established.""" log.debug('Starting Zookeeper conncetion...') self._connect(self._lazy) @_health_check def set(self, node, data, state, type): """Registers a supplied node (full path and nodename). Registers the supplied node-name with Zookeeper and converts the supplied data into JSON-text. Args: node: A string representing the node name and service port (/services/foo/host:port) data: A dict with any additional data you wish to register. state: True/False whether or not the node is actively listed in Zookeeper type: A Registration class object representing the type of node we're registering. Returns: True: registration was successful""" # Check if we're in read-only mode if self._readonly: raise exceptions.ReadOnly('In read-only mode, no writes allowed') # Check if the node is already there or not. If it is, we have to # figure out if we were the ones who registered it or not. If we are, # we leave it alone. If not, we attempt to delete it and register our # own. If we cannot do that, we throw an error. log.debug('Looking for Registration object for [%s] with [%s].' % (node, data)) if node in self._registrations: # The Registration objects can stop themselves in the event of # a failure. If they do, lets throw a message, toss the object, # and then let a new one be created. log.debug('[%s] already has Registration object.' % node) self._registrations[node].update(data=data, state=state) return True # Create a new registration object log.debug('Creating Registration object for [%s]' % node) self._registrations[node] = type(zk=self._zk, path=node, data=data, state=state) return True def _create_connection(self): """Create our 'zk' connection object. If the object does not exist, create it. If it does exist, move on. """ if self._zk: return # Define our zookeeper client here so that it never gets overwritten self._zk = ZookeeperClient(hosts=self._server, timeout=self._timeout, read_only=self._readonly, handler=EventHandler(), retry_delay=0.1, retry_backoff=2, retry_max_delay=10) # Set up our rate limiting self._zk.set_rate_limiter(delay=self._rate_limit_time, calls=self._rate_limit_calls) # Get a lock handler self._run_lock = self._zk.handler.lock_object() # Watch for any connection state changes self._zk.add_listener(self._state_listener) def _connect(self, lazy): """Connect to Zookeeper. This function starts the connection to Zookeeper using the Kazoo start_async() function. By using start_async(), we can continue to re-try the connection if it fails either on the initial __init__ of the module, or if it fails after the object has been running for a while. Args: lazy: True/False - determines whether or not we continue to try to connect in the background if the initial connection fails. """ log.info('Connecting to Zookeeper Service (%s)' % self._server) # Create our zookeeper connection object self._create_connection() # Start our connection asynchronously self.event = self._zk.start_async() self.event.wait(timeout=self._timeout) # After the timeout above, check if we're connected. if not self._zk.connected: # If we allow lazy-connection mode, we'll continue to try to # connect in the background. In the foreground, we'll check if # theres a locally cached dict() file that we can grab some data # from so that we can function partially. if lazy: log.warning( 'Could not reach Zookeeper server. ' 'Starting up in crippled mode. ' 'Will continue to try to connect in the background.') else: # If lazy mode is False, then we stop trying to connect to # Zookeeper and raise an exception. The client can deal with # what-to-do at this point. self._zk.stop() raise exceptions.NoConnection( 'Could not connect to Zookeeper') @_health_check def _setup_auth(self): """Set up our credentials with the Zookeeper service. If credentials were passwed to us, authenticate with Zookeeper. These credentials do not have to exist in the system, they're compared against other credentials to validate whether two users are the same, or whether a particular set of credentials has access to a particular node. """ with self._run_lock: if self._username and self._password: # If an ACL was provided, we'll use it. If not though, we'll # create our own ACL described below: # # This ACL essentially allows our USERNAME+PASSWORD combo to # completely own any nodes that were also created with the same # USERNAME+PASSWORD combo. This means that if all of your # production machines share a particular username/password, # they can each mess with the other machines node # registrations. # # Its highly recommended that you break up your server farms # into different permission groups. ACL = kazoo.security.make_digest_acl(u'%s' % self._username, u'%s' % self._password, all=True) # This allows *all* users to read child nodes, but disallows # them from reading, updating permissions, deleting child # nodes, or writing to child nodes that they do not own. READONLY_ACL = kazoo.security.make_acl(u'world', u'anyone', create=False, delete=False, write=False, read=True, admin=False) log.debug('Credentials were supplied, adding auth.') self._zk.retry(self._zk.add_auth_async, 'digest', "%s:%s" % (self._username, self._password)) if not self._acl: self._acl = (ACL, READONLY_ACL) # If an ACL was providfed, or we dynamically generated one with the # username/password, then set it. if self._acl: self._zk.default_acl = self._acl def _state_listener(self, state): """Listens for state changes about our connection. If our client becomes disconnected, we set a local variable that lets the rest of the code know to not try to run any Zookeeper commands until the service is back up.""" message = 'Zookeeper connection state changed' if state == KazooState.SUSPENDED: # In this state, just mark that we can't handle any 'writes' right # now but that we might come back to life soon. log.warning('%s: %s' % (message, state)) self._conn_state = False elif state == KazooState.LOST: # If we enter the LOST state, we've started a whole new session # with the Zookeeper server. Watches are re-established auto- # magically. Registered paths are re-established by their own # Registration control objects. log.warning('%s: %s' % (message, state)) self._conn_state = False else: log.info('%s: %s' % (message, state)) self._conn_state = True # We've re-connected, so re-configure our auth digest settings self._setup_auth() # We are not allowed to call any blocking calls in this callback # because it is actually triggered by the thread that holds onto # the Zookeeper connection -- and that causes a deadlock. # # In order to re-register our Watchers, we use the Kazoo spawn() # function. This runs the function in a separate thread and allows # the state_listener function to return quickly. self._zk.handler.spawn(self._convert_dummywatchers_to_watchers) # Execute any of our state-listener callbacks now that we've finished # updating the internal state of our connection. def execute_state_callbacks(state): """Sequentially executes the callback functions. args: state: Boolean of our connection state """ log.debug('Configured connection state callbacks: %s' % self._conn_state_callbacks) for callback in self._conn_state_callbacks: log.debug('Calling [%s] with new connection state: %s' % (callback, state)) callback(state) self._zk.handler.spawn(execute_state_callbacks(self._conn_state)) def _get_state(self, callback=None): """Returns the current connection state to Zookeeper. Optionally, if a callback function is supplied we will add it to the callback list for the _state_listener() function. This will trigger the callback immediately as well. args: callback: A reference to a function to call when the state changes. returns: Boolean of the connection state. """ if callback and callback in self._conn_state_callbacks: log.debug('[%s] already in state listener callbacks.' % callback) return self._conn_state if callback: self._conn_state_callbacks.append(callback) callback(self._conn_state) return self._conn_state def add_callback(self, path, callback): """Adds a callback in the event of a path change. Adds a callback to a given watcher. If the watcher doesnt exist, we create it with that callback. Either way, your callback is immediately executed with the service data. Args: path: A string reprsenting the path to watch for changes. callback: Reference to the function to callback to. """ if path in self._watchers: log.debug('Found [%s] in watchers. Adding callback.' % path) self._watchers[path].add_callback(callback) else: log.debug('No existing watcher for [%s] exists. Creating.' % path) self.get(path, callback=callback) def get(self, path, callback=None): """Retrieves a list of nodes (or a single node) in dict() form. Creates a Watcher object for the supplied path and returns the data for the requested path. Triggers callback immediately. Args: path: A string representing the path to the servers. callback: (optional) reference to function to call if the path changes. Returns: nd_service_registry.Watcher.get() dict object """ # Return the object from our cache, if it's there log.debug('[%s] Checking for existing object...' % path) if path in self._watchers: log.debug('Found [%s] in cache: %s' % (path, self._watchers[path].get())) # If a callback was suplied, but we already have a Watcher object, # add that callback to the existing object. if callback: self._watchers[path].add_callback(callback) # Return the Watcher object get() data. return self._watchers[path].get() try: # Go get a Watcher object since one doesnt already exist self._watchers[path] = self._get_watcher(path, callback) return self._watchers[path].get() except exceptions.NoConnection as e: # Get a DummyWatcher cached object instead log.warning('Health Check failed: %s' % e) try: log.info('[%s] Loading from cache instead' % path) watcher = self._get_dummywatcher_from_cache(path, callback) self._watchers[path] = watcher return self._watchers[path].get() except exceptions.ServiceRegistryException as e: # Failure getting the DummyWatcher from our cache, so now # create a blank DummyWatcher just to keep track of this # request. When the connection comes back, this DummyWatcher # will be converted into a real Watcher object and will notify # the callback appropriately. log.error('Unable to retrieve [%s] from Zookeeper or cache: ' '%s' % (path, e)) watcher = self._get_dummywatcher(path, callback) self._watchers[path] = watcher return False @_health_check def _get_watcher(self, path, callback=None): """Creates a Watcher object for the supplid path. Creates a Watcher object for the supplied path and returns the object. Triggers callbacks immediately. This is broken into its own function so that it can leverage the @_health_check decorator. This function should never be called from outside of this class. Args: path: A string representing the path to the servers. callback: (optional) reference to function to call if the path changes. Returns: nd_service_registry.Watcher object """ # Ok, so the cache is missing the key. Lets look for it in Zookeeper log.debug('[%s] Creating Watcher object...' % path) watcher = Watcher(self._zk, path, watch_children=True, callback=callback) # Always register our dictionary saver callback, in addition to # whatever our user has supplied watcher.add_callback(self._save_watcher_to_dict) return watcher @_health_check def _get_lock(self, path, name, simultaneous, wait): """Retrieves a lock semaphore-style object from the supplied path. This method creates our Lock object and returns it. It is not meant to be used directly though. Args: path: A string representing the path for the lock. name: Optional string representing the server identifier. simultaneous: Int representing how many concurrent locks can occur on this path. Returns: nd_service_registry.Lock object """ # Return the object from our cache, if it's there log.debug('[%s] Checking for existing object...' % path) if path in self._locks: log.debug('Found [%s] in cache: %s' % (path, str(self._locks[path]))) return self._locks[path] # Go create a Lock object and store it log.debug('[%s] Creating Lock object...' % path) self._locks[path] = Lock(self._zk, path, name, simultaneous, wait) return self._locks[path]