def zk_main_02(): url = '127.0.0.1:2181' zk_test_node = '/zk_test_02' zkc = KazooClient(hosts=url, timeout=3) zkc.start() print('\npid=%d, zookeeper version: %s' % (os.getpid(), zkc.server_version())) try: # ephemeral=False: cannot create child for ephemeral node ret_path = zkc.create(path=zk_test_node, value=b'zk_test_02_init', ephemeral=False) print('\ncreate a zk test node:', ret_path) time.sleep(1) zk_watcher = zkWatcher(url, zk_test_node) p = multiprocessing.Process(target=zk_watcher.exec) p.start() time.sleep(1) zk_test_02(zkc, zk_test_node) if p is not None: p.join(timeout=8) if p.is_alive(): print('pid=%d is pending, and kill!' % p.pid) p.kill() finally: if zkc.exists(zk_test_node, watch=None): zkc.delete(zk_test_node, recursive=True) # close zk session, and all ephemeral nodes will be removed zkc.stop()
def run(args, helpers): version = None if KazooClient is None: logging.warning( "Skipping zookeper discovery as Kazoo python library is absent") else: try: zk = KazooClient(hosts="%s:%d" % (args.ip, args.port), read_only=True) zk.start() version = zk.server_version() logging.info("Detected Zookeeper version: %s" % ('.'.join(str(i) for i in version))) except Exception: logging.exception( "Zookeeper might not be running a client listener on this port" ) return (version)
def exec(self): zkc = KazooClient(hosts=self._url, timeout=3) zkc.start() print('\npid=%d, zookeeper version: %s' % (os.getpid(), zkc.server_version())) try: self._list_ori = zkc.get_children(self._zk_node) print('\nattach data and child watch') # watch func trigger for each change DataWatch(client=zkc, path=self._zk_node, func=self._data_change) ChildrenWatch(client=zkc, path=self._zk_node, func=self._child_change) print('\nzk watch is running ...') time.sleep(6) finally: if zkc is not None: zkc.stop()
def run(zk_host): client = KazooClient(zk_host) client.start(timeout=15) zk_server_version = client.server_version() client.add_listener() print 'Server Version: {0}'.format(zk_server_version) queue = Queue() queue.enqueue('/') while queue.size() > 0: path = queue.dequeue() path_node = client.get(path) print '[{0}] {1}'.format(path_node[1].created, path) path_children = client.get_children(path) for child in path_children: queue.enqueue(path + child + '/') client.stop()
def zk_test_01(url, test_node): # if run in multi process, should init a new zk client. zkc = KazooClient(hosts=url, timeout=3) zkc.start() print('\npid=%d, zookeeper version: %s' % (os.getpid(), zkc.server_version())) try: stat = zkc.set(test_node, b'zk_test_01_new_1') print('\nzk test node updated, and stat version:', stat.version) time.sleep(2) stat = zkc.set(test_node, b'zk_test_01_new_2') print('\nzk test node updated, and stat version:', stat.version) time.sleep(1) data, stat = zkc.get(test_node, watch=None) print('\nzk node stat:') print('\tnode data="%s", length=%d' % (data, stat.dataLength)) print('\tnode stat version:', stat.version) print() finally: if zkc is not None: zkc.stop()
class ZookeeperServiceRegistry(BaseServiceRegistry): def __init__(self, hosts=DEFAULT_HOSTS, chroot=DEFAULT_CHROOT): super(ZookeeperServiceRegistry, self).__init__() self.chroot = chroot self.client = KazooClient( hosts=hosts, handler=SequentialGeventHandler(), ) self.client.add_listener(self.on_kazoo_state_change) self.start_count = 0 @classmethod def from_config(cls, config, **kwargs): return cls(hosts=config.get('hosts', DEFAULT_HOSTS), chroot=config.get('chroot', DEFAULT_CHROOT), **kwargs) def on_start(self, timeout=10): self.start_count += 1 if self.start_count > 1: return started = self.client.start_async() started.wait(timeout=timeout) if not self.client.connected: raise RuntimeError('could not connect to zookeeper') logger.debug('connected to zookeeper (version=%s)', '.'.join(map(str, self.client.server_version()))) def on_stop(self): self.start_count -= 1 if self.start_count != 0: return self.client.stop() def on_kazoo_state_change(self, state): logger.info('kazoo connection state changed to %s', state) def on_service_type_watch(self, service, event): try: if event.type == EventType.CHILD: # FIXME: figure out proper retry strategy self.client.retry(self.lookup, service.container, service) except Exception: logger.exception('error in service type watcher') def on_service_watch(self, service, event): try: prefix, service_type, identity = event.path.rsplit('/', 2) if event.type == EventType.DELETED: service.remove(identity) except Exception: logger.exception('error in service watcher') def _get_service_znode(self, service, service_type, identity): path = self._get_zk_path(service_type, identity) result = self.client.get_async(path, watch=functools.partial( self.on_service_watch, service)) value, znode = result.get() items = six.iteritems(json.loads(value.decode('utf-8'))) return {str(k): str(v) for k, v in items} def discover(self, container): result = self.client.get_children_async(path='%s/services' % self.chroot, ) return list(result.get()) def lookup(self, container, service, watch=True, timeout=1): def child_watch(event): print(event) service_type = service.service_type result = self.client.get_children_async( path='%s/services/%s' % (self.chroot, service_type), watch=functools.partial(self.on_service_type_watch, service), ) try: names = result.get(timeout=timeout) except NoNodeError: raise LookupFailure(None, "failed to resolve %s" % service.service_type) logger.info("lookup %s %r", service_type, names) identities = set(service.identities()) for name in names: kwargs = self._get_service_znode(service, service_type, name) identity = kwargs.pop('identity') service.update(identity, **kwargs) try: identities.remove(identity) except KeyError: pass for identity in identities: service.remove(identity) return service def _get_zk_path(self, service_type, identity): return '%s/services/%s/%s' % (self.chroot, service_type, identity) def register(self, container, service_type, timeout=1): path = self._get_zk_path(service_type, container.identity) value = json.dumps({ 'endpoint': container.endpoint, 'identity': container.identity, 'log_endpoint': container.log_endpoint, }) result = self.client.create_async(path, value.encode('utf-8'), ephemeral=True, makepath=True) # FIXME: result.set_exception(RegistrationFailure()) result.get(timeout=timeout) def unregister(self, container, service_type, timeout=1): path = self._get_zk_path(service_type, container.identity) result = self.client.delete_async(path) result.set_exception(RegistrationFailure()) result.get(timeout=timeout)
class ZookeeperWatcher(object): zoo_client = None # The KazooClient to manage the config point_path = None # Zookeeper path to pointed to file pointed_at_expired = None # is True when the assignment has been set to # None but we cannot remove the config listener valid_handler = None # the function to call when the validity changes config_handler = None # the function to call when the config changes error_handler = None # the function to call when an error occurs in reading valid_file = False # the current state of the ConfigWatcher with ZK do_not_restart = False # used when closing via ^C old_data = '' # The current file contents, to see if a change occurred old_pointed = '' # the current pointed path, to see if change occurred INVALID_PATH = "Invalid pointer path" INVALID_GET = "Invalid get on file path" BAD_CONNECTION = "Connection interrupted with Zookeeper, re-establishing" def __init__(self, hosts, filepath, valid_handler=None, config_handler=None, error_handler=None, pointer=False, ensure=False, valid_init=True): ''' Zookeeper file watcher, used to tell a program their zookeeper file has changed. Can be used to watch a single file, or both a file and path of its contents. Manages all connections, drops, reconnections for you. @param hosts: The zookeeper hosts to use @param filepath: The full path to the file to watch @param valid_handler: The method to call for a 'is valid' state change @param config_handler: The method to call when a content change occurs @param error_handler: The method to call when an error occurs @param pointer: Set to true if the file contents are actually a path to another zookeeper file, where the real config resides @param ensure: Set to true for the ZooWatcher to create the watched file @param valid_init: Ensure the client can connect to Zookeeper first try Ex 1. /stuff/A: "stuff I care about" Ex 2. /stuff/A: "/other/stuff", /other/stuff: "contents I care about" - in Ex 2 you care about /other/stuff contents but are only aware of your assignment /stuff/A You can use this class as any combination of event driven or polling. Polling: In the main loop of your program, check if is_valid() is True, otherwise clear your contents as there is some ZK error. Event: You will be notified via the various handlers when content changes. ''' self.hosts = hosts self.my_file = filepath self.pointer = pointer self.ensure = ensure self.valid_handler = valid_handler self.config_handler = config_handler self.error_handler = error_handler if valid_init: # this will throw an exception if it can't start right away self.zoo_client = KazooClient(hosts=self.hosts) self.zoo_client.start() self.threaded_start(no_init=True) def threaded_start(self, no_init=False): ''' Spawns a worker thread to set up the zookeeper connection ''' thread = Thread(target=self.init_connections, kwargs={ 'no_init': no_init}) thread.setDaemon(True) thread.start() thread.join() def init_connections(self, no_init=False): ''' Sets up the initial Kazoo Client and watches ''' success = False self.set_valid(False) if not no_init: if self.zoo_client: self.zoo_client.remove_listener(self.state_listener) self.old_data = '' self.old_pointed = '' while not success: try: if self.zoo_client is None: self.zoo_client = KazooClient(hosts=self.hosts) self.zoo_client.start() else: # self.zoo_client.stop() self.zoo_client._connection.connection_stopped.set() self.zoo_client.close() self.zoo_client = KazooClient(hosts=self.hosts) self.zoo_client.start() except Exception as e: log.error("ZKWatcher Exception: " + e.message) sleep(1) continue self.setup() success = self.update_file(self.my_file) sleep(5) else: self.setup() self.update_file(self.my_file) def setup(self): ''' Ensures the path to the watched file exists and we have a state listener ''' self.zoo_client.add_listener(self.state_listener) if self.ensure: self.zoo_client.ensure_path(self.my_file) def state_listener(self, state): ''' Restarts the session if we get anything besides CONNECTED ''' if state == KazooState.SUSPENDED: self.set_valid(False) self.call_error(self.BAD_CONNECTION) elif state == KazooState.LOST and not self.do_not_restart: self.threaded_start() elif state == KazooState.CONNECTED: # This is going to throw a SUSPENDED kazoo error # which will cause the sessions to be wiped and re established. # Used b/c of massive connection pool issues self.zoo_client.stop() def is_valid(self): ''' @return: True if the currently watch file is valid ''' return self.valid_file def ping(self): ''' Simple command to test if the zookeeper session is able to connect at this very moment ''' try: # dummy ping to ensure we are still connected self.zoo_client.server_version() return True except KazooException: return False def close(self, kill_restart=True): ''' Use when you would like to close everything down @param kill_restart= Prevent kazoo restarting from occurring ''' self.do_not_restart = kill_restart self.zoo_client.stop() self.zoo_client.close() def get_file_contents(self, pointer=False): ''' Gets any file contents you care about. Defaults to the main file @param pointer: The the contents of the file pointer, not the pointed at file @return: A string of the contents ''' if self.pointer: if pointer: return self.old_pointed else: return self.old_data else: return self.old_data def watch_file(self, event): ''' Fired when changes made to the file ''' if not self.update_file(self.my_file): self.threaded_start() def update_file(self, path): ''' Updates the file watcher and calls the appropriate method for results @return: False if we need to keep trying the connection ''' try: # grab the file result, stat = self.zoo_client.get(path, watch=self.watch_file) except ZookeeperError: self.set_valid(False) self.call_error(self.INVALID_GET) return False if self.pointer: if result is not None and len(result) > 0: self.pointed_at_expired = False # file is a pointer, go update and watch other file self.point_path = result if self.compare_pointer(result): self.update_pointed() else: self.pointed_at_expired = True self.old_pointed = '' self.old_data = '' self.set_valid(False) self.call_error(self.INVALID_PATH) else: # file is not a pointer, return contents if self.compare_data(result): self.call_config(result) self.set_valid(True) return True def watch_pointed(self, event): ''' Fired when changes made to pointed file ''' self.update_pointed() def update_pointed(self): ''' Grabs the latest file contents based on the pointer uri ''' # only grab file if our pointer is still good (not None) if not self.pointed_at_expired: try: conf_string, stat2 = self.zoo_client.get(self.point_path, watch=self.watch_pointed) except ZookeeperError: self.old_data = '' self.set_valid(False) self.pointed_at_expired = True self.call_error(self.INVALID_PATH) return if self.compare_data(conf_string): self.call_config(conf_string) self.set_valid(True) def set_valid(self, boolean): ''' Sets the state and calls the change if needed @param bool: The state (true or false) ''' old_state = self.is_valid() self.valid_file = boolean if old_state != self.valid_file: self.call_valid(self.valid_file) def call_valid(self, state): ''' Calls the valid change function passed in @param valid_state: The new config ''' if self.valid_handler is not None: self.valid_handler(self.is_valid()) def call_config(self, new_config): ''' Calls the config function passed in @param new_config: The new config ''' if self.config_handler is not None: self.config_handler(new_config) def call_error(self, message): ''' Calls the error function passed in @param message: The message to throw ''' if self.error_handler is not None: self.error_handler(message) def compare_data(self, data): ''' Compares the string data @return: True if the data is different ''' if self.old_data != data: self.old_data = data return True return False def compare_pointer(self, data): ''' Compares the string data @return: True if the data is different ''' if self.old_pointed != data: self.old_pointed = data return True return False
class ZookeeperWatcher(object): zoo_client = None # The KazooClient to manage the config point_path = None # Zookeeper path to pointed to file pointed_at_expired = None # is True when the assignment has been set to # None but we cannot remove the config listener valid_handler = None # the function to call when the validity changes config_handler = None # the function to call when the config changes error_handler = None # the function to call when an error occurs in reading valid_file = False # the current state of the ConfigWatcher with ZK do_not_restart = False # used when closing via ^C old_data = '' # The current file contents, to see if a change occurred old_pointed = '' # the current pointed path, to see if change occurred INVALID_PATH = "Invalid pointer path" INVALID_GET = "Invalid get on file path" BAD_CONNECTION = "Connection interrupted with Zookeeper, re-establishing" def __init__(self, hosts, filepath, valid_handler=None, config_handler=None, error_handler=None, pointer=False, ensure=False, valid_init=True): ''' Zookeeper file watcher, used to tell a program their zookeeper file has changed. Can be used to watch a single file, or both a file and path of its contents. Manages all connections, drops, reconnections for you. @param hosts: The zookeeper hosts to use @param filepath: The full path to the file to watch @param valid_handler: The method to call for a 'is valid' state change @param config_handler: The method to call when a content change occurs @param error_handler: The method to call when an error occurs @param pointer: Set to true if the file contents are actually a path to another zookeeper file, where the real config resides @param ensure: Set to true for the ZooWatcher to create the watched file @param valid_init: Ensure the client can connect to Zookeeper first try Ex 1. /stuff/A: "stuff I care about" Ex 2. /stuff/A: "/other/stuff", /other/stuff: "contents I care about" - in Ex 2 you care about /other/stuff contents but are only aware of your assignment /stuff/A You can use this class as any combination of event driven or polling. Polling: In the main loop of your program, check if is_valid() is True, otherwise clear your contents as there is some ZK error. Event: You will be notified via the various handlers when content changes. ''' self.hosts = hosts self.my_file = filepath self.pointer = pointer self.ensure = ensure self.valid_handler = valid_handler self.config_handler = config_handler self.error_handler = error_handler if valid_init: # this will throw an exception if it can't start right away self.zoo_client = KazooClient(hosts=self.hosts) self.zoo_client.start() self.threaded_start(no_init=True) def threaded_start(self, no_init=False): ''' Spawns a worker thread to set up the zookeeper connection ''' thread = Thread(target=self.init_connections, kwargs={'no_init': no_init}) thread.setDaemon(True) thread.start() thread.join() def init_connections(self, no_init=False): ''' Sets up the initial Kazoo Client and watches ''' success = False self.set_valid(False) if not no_init: if self.zoo_client: self.zoo_client.remove_listener(self.state_listener) self.old_data = '' self.old_pointed = '' while not success: try: if self.zoo_client is None: self.zoo_client = KazooClient(hosts=self.hosts) self.zoo_client.start() else: # self.zoo_client.stop() self.zoo_client._connection.connection_stopped.set() self.zoo_client.close() self.zoo_client = KazooClient(hosts=self.hosts) self.zoo_client.start() except Exception as e: log.error("ZKWatcher Exception: " + e.message) sleep(1) continue self.setup() success = self.update_file(self.my_file) sleep(5) else: self.setup() self.update_file(self.my_file) def setup(self): ''' Ensures the path to the watched file exists and we have a state listener ''' self.zoo_client.add_listener(self.state_listener) if self.ensure: self.zoo_client.ensure_path(self.my_file) def state_listener(self, state): ''' Restarts the session if we get anything besides CONNECTED ''' if state == KazooState.SUSPENDED: self.set_valid(False) self.call_error(self.BAD_CONNECTION) elif state == KazooState.LOST and not self.do_not_restart: self.threaded_start() elif state == KazooState.CONNECTED: # This is going to throw a SUSPENDED kazoo error # which will cause the sessions to be wiped and re established. # Used b/c of massive connection pool issues self.zoo_client.stop() def is_valid(self): ''' @return: True if the currently watch file is valid ''' return self.valid_file def ping(self): ''' Simple command to test if the zookeeper session is able to connect at this very moment ''' try: # dummy ping to ensure we are still connected self.zoo_client.server_version() return True except KazooException: return False def close(self, kill_restart=True): ''' Use when you would like to close everything down @param kill_restart= Prevent kazoo restarting from occurring ''' self.do_not_restart = kill_restart self.zoo_client.stop() self.zoo_client.close() def get_file_contents(self, pointer=False): ''' Gets any file contents you care about. Defaults to the main file @param pointer: The the contents of the file pointer, not the pointed at file @return: A string of the contents ''' if self.pointer: if pointer: return self.old_pointed else: return self.old_data else: return self.old_data def watch_file(self, event): ''' Fired when changes made to the file ''' if not self.update_file(self.my_file): self.threaded_start() def update_file(self, path): ''' Updates the file watcher and calls the appropriate method for results @return: False if we need to keep trying the connection ''' try: # grab the file result, stat = self.zoo_client.get(path, watch=self.watch_file) except ZookeeperError: self.set_valid(False) self.call_error(self.INVALID_GET) return False if self.pointer: if result is not None and len(result) > 0: self.pointed_at_expired = False # file is a pointer, go update and watch other file self.point_path = result if self.compare_pointer(result): self.update_pointed() else: self.pointed_at_expired = True self.old_pointed = '' self.old_data = '' self.set_valid(False) self.call_error(self.INVALID_PATH) else: # file is not a pointer, return contents if self.compare_data(result): self.call_config(result) self.set_valid(True) return True def watch_pointed(self, event): ''' Fired when changes made to pointed file ''' self.update_pointed() def update_pointed(self): ''' Grabs the latest file contents based on the pointer uri ''' # only grab file if our pointer is still good (not None) if not self.pointed_at_expired: try: conf_string, stat2 = self.zoo_client.get( self.point_path, watch=self.watch_pointed) except ZookeeperError: self.old_data = '' self.set_valid(False) self.pointed_at_expired = True self.call_error(self.INVALID_PATH) return if self.compare_data(conf_string): self.call_config(conf_string) self.set_valid(True) def set_valid(self, boolean): ''' Sets the state and calls the change if needed @param bool: The state (true or false) ''' old_state = self.is_valid() self.valid_file = boolean if old_state != self.valid_file: self.call_valid(self.valid_file) def call_valid(self, state): ''' Calls the valid change function passed in @param valid_state: The new config ''' if self.valid_handler is not None: self.valid_handler(self.is_valid()) def call_config(self, new_config): ''' Calls the config function passed in @param new_config: The new config ''' if self.config_handler is not None: self.config_handler(new_config) def call_error(self, message): ''' Calls the error function passed in @param message: The message to throw ''' if self.error_handler is not None: self.error_handler(message) def compare_data(self, data): ''' Compares the string data @return: True if the data is different ''' if self.old_data != data: self.old_data = data return True return False def compare_pointer(self, data): ''' Compares the string data @return: True if the data is different ''' if self.old_pointed != data: self.old_pointed = data return True return False
#!/bin/env python # -*- coding: UTF-8 -*- from kazoo.client import KazooClient from kazoo.exceptions import * import traceback zk = KazooClient(hosts='127.0.0.1:2181') zk.start() # children = zk.get_children('/') print "zk version:" + (".".join(str(x) for x in zk.server_version())) print "client id :" + str(zk.client_id[0]) servicePath = "/{root}/{service}/provider".format(root="zkpyro", service="serviceName01") nodeId = zk.client_id[0] nodePath = "{servicePath}/{node}".format(servicePath=servicePath, node=nodeId) print "servicePath:" + servicePath try: zk.create(nodePath, ephemeral=True, makepath=True) zk.set(nodePath, "pyro.url") print "Znode value:" + zk.get(nodePath)[0] except NodeExistsError: traceback.print_stack() zk.stop()
class ZookeeperServiceRegistry(BaseServiceRegistry): def __init__(self, hosts=DEFAULT_HOSTS, chroot=DEFAULT_CHROOT): super(ZookeeperServiceRegistry, self).__init__() self.chroot = chroot self.client = KazooClient( hosts=hosts, handler=SequentialGeventHandler(), ) self.client.add_listener(self.on_kazoo_state_change) self.start_count = 0 @classmethod def from_config(cls, config, **kwargs): return cls( hosts=config.get('hosts', DEFAULT_HOSTS), chroot=config.get('chroot', DEFAULT_CHROOT), **kwargs ) def on_start(self, timeout=10): self.start_count += 1 if self.start_count > 1: return started = self.client.start_async() started.wait(timeout=timeout) if not self.client.connected: raise RuntimeError('could not connect to zookeeper') logger.debug('connected to zookeeper (version=%s)', '.'.join(map(str, self.client.server_version()))) def on_stop(self): self.start_count -= 1 if self.start_count != 0: return self.client.stop() def on_kazoo_state_change(self, state): logger.info('kazoo connection state changed to %s', state) def on_service_type_watch(self, service, event): try: if event.type == EventType.CHILD: # FIXME: figure out proper retry strategy self.client.retry(self.lookup, service.container, service) except Exception: logger.exception('error in service type watcher') def on_service_watch(self, service, event): try: prefix, service_type, identity = event.path.rsplit('/', 2) if event.type == EventType.DELETED: service.remove(identity) except Exception: logger.exception('error in service watcher') def _get_service_znode(self, service, service_type, identity): path = self._get_zk_path(service_type, identity) result = self.client.get_async( path, watch=functools.partial(self.on_service_watch, service)) value, znode = result.get() items = six.iteritems(json.loads(value.decode('utf-8'))) return {str(k): str(v) for k, v in items} def discover(self, container): result = self.client.get_children_async( path='%s/services' % self.chroot, ) return list(result.get()) def lookup(self, container, service, watch=True, timeout=1): def child_watch(event): print(event) service_type = service.service_type result = self.client.get_children_async( path='%s/services/%s' % (self.chroot, service_type), watch=functools.partial(self.on_service_type_watch, service), ) try: names = result.get(timeout=timeout) except NoNodeError: raise LookupFailure(None, "failed to resolve %s" % service.service_type) logger.info("lookup %s %r", service_type, names) identities = set(service.identities()) for name in names: kwargs = self._get_service_znode(service, service_type, name) identity = kwargs.pop('identity') service.update(identity, **kwargs) try: identities.remove(identity) except KeyError: pass for identity in identities: service.remove(identity) return service def _get_zk_path(self, service_type, identity): return '%s/services/%s/%s' % (self.chroot, service_type, identity) def register(self, container, service_type, timeout=1): path = self._get_zk_path(service_type, container.identity) value = json.dumps({ 'endpoint': container.endpoint, 'identity': container.identity, 'log_endpoint': container.log_endpoint, }) result = self.client.create_async( path, value.encode('utf-8'), ephemeral=True, makepath=True) # FIXME: result.set_exception(RegistrationFailure()) result.get(timeout=timeout) def unregister(self, container, service_type, timeout=1): path = self._get_zk_path(service_type, container.identity) result = self.client.delete_async(path) result.set_exception(RegistrationFailure()) result.get(timeout=timeout)
def watch(zookeepers, node, leader=False): """ Watch a particular zookeeper node for changes. """ zk_hosts = parse_zk_hosts(zookeepers, leader=leader)[0] def my_listener(state): if state == KazooState.LOST: # Register somewhere that the session was lost print(style_text('Connection Lost', ERROR_STYLE, pad=2)) elif state == KazooState.SUSPENDED: # Handle being disconnected from Zookeeper print(style_text('Connection Suspended', ERROR_STYLE, pad=2)) else: # Handle being connected/reconnected to Zookeeper # what are we supposed to do here? print(style_text('Connected/Reconnected', INFO_STYLE, pad=2)) zk = KazooClient(hosts=zk_hosts, read_only=True) try: zk.start() except KazooTimeoutError as e: print('ZK Timeout host: [%s], %s' % (host, e)) zk_ver = '.'.join(map(str, zk.server_version())) zk_host = zk.hosts[zk.last_zxid] zk_host = ':'.join(map(str, zk_host)) zk.add_listener(my_listener) # check if the node exists ... if not zk.exists(node): node_str = style_text(node, BLUE_STYLE, restore=ERROR_STYLE) zk_str = style_text(zk_host, BLUE_STYLE, restore=ERROR_STYLE) print('') print( style_text('No node [%s] on %s' % (node_str, zk_str), ERROR_STYLE, pad=2)) return print(style_header('Watching [%s] on %s v%s' % (node, zk_host, zk_ver))) # put a watch on my znode children = zk.get_children(node) # If there are children, watch them. if children or node.endswith('/'): @zk.ChildrenWatch(node) def watch_children(children): global WATCH_COUNTER WATCH_COUNTER += 1 if WATCH_COUNTER <= 1: child_watch_str = 'Child Nodes:' else: child_watch_str = 'Node Watch Event: ' children.sort() print('') print(style_text(child_watch_str, TITLE_STYLE)) for ch in children: print(style_text(ch, INFO_STYLE, lpad=2)) print('') else: # otherwise watch the node itself. @zk.DataWatch(node) def watch_data(data, stat, event): global WATCH_COUNTER WATCH_COUNTER += 1 data = data.decode('utf-8') if WATCH_COUNTER <= 1: data_watch_str = 'Content: (%s)' else: data_watch_str = 'Data Watch Event: (v%s)' print('') print(style_text(data_watch_str % stat.version, TITLE_STYLE)) print(style_multiline(data, INFO_STYLE, lpad=2)) print('') CHAR_WIDTH = 60 counter = 0 while True: # draw a .... animation while we wait, so the user knows its working. counter += 1 if not counter % CHAR_WIDTH: print('\r', ' ' * CHAR_WIDTH, '\r', end='') print(style_text('.', INFO_STYLE), end='') time.sleep(0.05) zk.stop()
class USSMetadataManager(object): """Interfaces with the locking system to get, put, and delete USS metadata. Metadata gets/stores/deletes the USS information for a partiular grid, including current version number, a list of USSs with active operations, and the endpoints to get that information. Locking is assured through a snapshot token received when getting, and used when putting. """ def __init__(self, connectionstring=DEFAULT_CONNECTION, testgroupid=None): """Initializes the class. Args: connectionstring: Zookeeper connection string - server:port,server:port,... testgroupid: ID to use if in test mode, none for normal mode """ if testgroupid: self.set_testmode(testgroupid) if not connectionstring: connectionstring = DEFAULT_CONNECTION log.debug( 'Creating metadata manager object and connecting to zookeeper...') try: if set(BAD_CHARACTER_CHECK) & set(connectionstring): raise ValueError self.zk = KazooClient(hosts=connectionstring, timeout=CONNECTION_TIMEOUT) self.zk.add_listener(self.zookeeper_connection_listener) self.zk.start() if testgroupid: self.delete_testdata(testgroupid) except KazooTimeoutError: log.error( 'Unable to connect to zookeeper using %s connection string...', connectionstring) raise except ValueError: log.error('Connection string %s seems invalid...', connectionstring) raise def __del__(self): log.debug( 'Destroying metadata manager object and disconnecting from zk...') self.zk.stop() def get_state(self): return self.zk.state def get_version(self): try: return True, self.zk.server_version() except KazooException as e: msg = str(e) return False, type(e).__name__ + (' ' + msg if msg else '') def set_verbose(self): log.setLevel(logging.DEBUG) def set_testmode(self, testgroupid='UNDEFINED_TESTER'): """Sets the mode to testing with the specific test ID, cannot be undone. Args: testgroupid: ID to use if in test mode, none for normal mode """ global GRID_PATH global CONNECTION_TIMEOUT # Adjust parameters specifically for the test GRID_PATH = TEST_BASE_PREFIX + testgroupid + USS_BASE_PREFIX log.debug('Setting test path to %s...', GRID_PATH) CONNECTION_TIMEOUT = 1.0 def zookeeper_connection_listener(self, state): if state == KazooState.LOST: # Register somewhere that the session was lost log.error('Lost connection with the zookeeper servers...') elif state == KazooState.SUSPENDED: # Handle being disconnected from Zookeeper log.error('Suspended connection with the zookeeper servers...') elif state == KazooState.CONNECTED: # Handle being connected/reconnected to Zookeeper log.info('Connection restored with the zookeeper servers...') def delete_testdata(self, testgroupid=None): """Removes the test data from the servers. Be careful when using this in parallel as it removes everything under the testgroupid, or everything if no tetgroupid is provided. Args: testgroupid: ID to use if in test mode, none will remove all test data """ if testgroupid: path = TEST_BASE_PREFIX + testgroupid else: path = TEST_BASE_PREFIX self.zk.delete(path, recursive=True) def get(self, z, x, y): """Gets the metadata and snapshot token for a GridCell. Reads data from zookeeper, including a snapshot token. The snapshot token is used as a reference when writing to ensure the data has not been updated between read and write. Args: z: zoom level in slippy tile format x: x tile number in slippy tile format y: y tile number in slippy tile format Returns: JSend formatted response (https://labs.omniti.com/labs/jsend) """ # TODO(hikevin): Change to use our own error codes and let the server # convert them to http error codes. For now, this is # at least in a standard JSend format. status = 500 if slippy_util.validate_slippy(z, x, y): (content, metadata) = self._get_raw(z, x, y) if metadata: try: m = uss_metadata.USSMetadata(content) status = 200 result = { 'status': 'success', 'sync_token': metadata.last_modified_transaction_id, 'data': m.to_json() } except ValueError: status = 424 else: status = 404 else: status = 400 if status != 200: result = self._format_status_code_to_jsend(status) return result def set(self, z, x, y, sync_token, uss_id, ws_scope, operation_format, operation_ws, earliest_operation, latest_operation): """Sets the metadata for a GridCell. Writes data, using the snapshot token for confirming data has not been updated since it was last read. Args: z: zoom level in slippy tile format x: x tile number in slippy tile format y: y tile number in slippy tile format sync_token: token retrieved in the original GET GridCellMetadata, uss_id: plain text identifier for the USS, ws_scope: scope to use to obtain OAuth token, operation_format: output format for operation ws (i.e. NASA, GUTMA), operation_ws: submitting USS endpoint where all flights in this cell can be retrieved from, earliest_operation: lower bound of active or planned flight timestamp, used for quick filtering conflicts. latest_operation: upper bound of active or planned flight timestamp, used for quick filtering conflicts. Returns: JSend formatted response (https://labs.omniti.com/labs/jsend) """ if slippy_util.validate_slippy(z, x, y): # first we have to get the cell (content, metadata) = self._get_raw(z, x, y) if metadata: # Quick check of the token, another is done on the actual set to be sure # but this check fails early and fast if str(metadata.last_modified_transaction_id) == str( sync_token): try: m = uss_metadata.USSMetadata(content) log.debug('Setting metadata for %s...', uss_id) if not m.upsert_operator( uss_id, ws_scope, operation_format, operation_ws, earliest_operation, latest_operation, z, x, y): log.error( 'Failed setting operator for %s with token %s...', uss_id, str(sync_token)) raise ValueError status = self._set_raw(z, x, y, m, metadata.version) except ValueError: status = 424 else: status = 409 else: status = 404 else: status = 400 if status == 200: # Success, now get the metadata back to send back result = self.get(z, x, y) else: result = self._format_status_code_to_jsend(status) return result def delete(self, z, x, y, uss_id): """Sets the metadata for a GridCell by removing the entry for the USS. Args: z: zoom level in slippy tile format x: x tile number in slippy tile format y: y tile number in slippy tile format uss_id: is the plain text identifier for the USS Returns: JSend formatted response (https://labs.omniti.com/labs/jsend) """ status = 500 if slippy_util.validate_slippy(z, x, y): # first we have to get the cell (content, metadata) = self._get_raw(z, x, y) if metadata: try: m = uss_metadata.USSMetadata(content) m.remove_operator(uss_id) # TODO(pelletierb): Automatically retry on delete status = self._set_raw(z, x, y, m, metadata.version) except ValueError: status = 424 else: status = 404 else: status = 400 if status == 200: # Success, now get the metadata back to send back (content, metadata) = self._get_raw(z, x, y) result = { 'status': 'success', 'sync_token': metadata.last_modified_transaction_id, 'data': m.to_json() } else: result = self._format_status_code_to_jsend(status) return result def get_multi(self, z, grids): """Gets the metadata and snapshot token for multiple GridCells. Reads data from zookeeper, including a composite snapshot token. The snapshot token is used as a reference when writing to ensure the data has not been updated between read and write. Args: z: zoom level in slippy tile format grids: list of (x,y) tiles to retrieve Returns: JSend formatted response (https://labs.omniti.com/labs/jsend) """ try: combined_meta, syncs = self._get_multi_raw(z, grids) log.debug('Found sync token %s for %d grids...', self._hash_sync_tokens(syncs), len(syncs)) result = { 'status': 'success', 'sync_token': self._hash_sync_tokens(syncs), 'data': combined_meta.to_json() } except ValueError as e: result = self._format_status_code_to_jsend(400, e.message) except IndexError as e: result = self._format_status_code_to_jsend(404, e.message) return result def set_multi(self, z, grids, sync_token, uss_id, ws_scope, operation_format, operation_ws, earliest_operation, latest_operation): """Sets multiple GridCells metadata at once. Writes data, using the hashed snapshot token for confirming data has not been updated since it was last read. Args: z: zoom level in slippy tile format grids: list of (x,y) tiles to update sync_token: token retrieved in the original get_multi, uss_id: plain text identifier for the USS, ws_scope: scope to use to obtain OAuth token, operation_format: output format for operation ws (i.e. NASA, GUTMA), operation_ws: submitting USS endpoint where all flights in this cell can be retrieved from, earliest_operation: lower bound of active or planned flight timestamp, used for quick filtering conflicts. latest_operation: upper bound of active or planned flight timestamp, used for quick filtering conflicts. Returns: JSend formatted response (https://labs.omniti.com/labs/jsend) """ log.debug('Setting multiple grid metadata for %s...', uss_id) try: # first, get the affected grid's sync tokens m, syncs = self._get_multi_raw(z, grids) del m # Quick check of the token, another is done on the actual set to be sure # but this check fails early and fast log.debug('Found sync token %d for %d grids...', self._hash_sync_tokens(syncs), len(syncs)) if str(self._hash_sync_tokens(syncs)) == str(sync_token): log.debug('Composite sync_token matches, continuing...') self._set_multi_raw(z, grids, syncs, uss_id, ws_scope, operation_format, operation_ws, earliest_operation, latest_operation) log.debug('Completed updating multiple grids...') else: raise KeyError('Composite sync_token has changed') combined_meta, new_syncs = self._get_multi_raw(z, grids) result = { 'status': 'success', 'sync_token': self._hash_sync_tokens(new_syncs), 'data': combined_meta.to_json() } except (KeyError, RolledBackError) as e: result = self._format_status_code_to_jsend(409, e.message) except ValueError as e: result = self._format_status_code_to_jsend(400, e.message) except IndexError as e: result = self._format_status_code_to_jsend(404, e.message) return result def delete_multi(self, z, grids, uss_id): """Sets multiple GridCells metadata by removing the entry for the USS. Removes the operator from multiple cells. Does not return 404 on not finding the USS in a cell, since this should be a remove all type function, as some cells might have the ussid and some might not. Args: z: zoom level in slippy tile format grids: list of (x,y) tiles to delete uss_id: is the plain text identifier for the USS Returns: JSend formatted response (https://labs.omniti.com/labs/jsend) """ log.debug('Deleting multiple grid metadata for %s...', uss_id) try: if not uss_id: raise ValueError('Invalid uss_id for deleting multi') for x, y in grids: if slippy_util.validate_slippy(z, x, y): (content, metadata) = self._get_raw(z, x, y) if metadata: m = uss_metadata.USSMetadata(content) m.remove_operator(uss_id) # TODO(pelletierb): Automatically retry on delete status = self._set_raw(z, x, y, m, metadata.version) else: raise ValueError('Invalid slippy grids for lookup') result = self.get_multi(z, grids) except ValueError as e: result = self._format_status_code_to_jsend(400, e.message) return result ###################################################################### ################ INTERNAL FUNCTIONS ######################### ###################################################################### def _get_raw(self, z, x, y): """Gets the raw content and metadata for a GridCell from zookeeper. Args: z: zoom level in slippy tile format x: x tile number in slippy tile format y: y tile number in slippy tile format Returns: content: USS metadata metadata: straight from zookeeper """ path = '%s/%s/%s/%s/%s' % (GRID_PATH, str(z), str(x), str(y), USS_METADATA_FILE) log.debug('Getting metadata from zookeeper@%s...', path) try: c, m = self.zk.get(path) except NoNodeError: self.zk.ensure_path(path) c, m = self.zk.get(path) if c: log.debug('Received raw content and metadata from zookeeper: %s', c) if m: log.debug('Received raw metadata from zookeeper: %s', m) return c, m def _set_raw(self, z, x, y, m, version): """Grabs the lock and updates the raw content for a GridCell in zookeeper. Args: z: zoom level in slippy tile format x: x tile number in slippy tile format y: y tile number in slippy tile format m: metadata object to write version: the metadata version verified from the sync_token match Returns: 200 for success, 409 for conflict, 408 for unable to get the lock """ path = '%s/%s/%s/%s/%s' % (GRID_PATH, str(z), str(x), str(y), USS_METADATA_FILE) try: log.debug('Setting metadata to %s...', str(m)) self.zk.set(path, json.dumps(m.to_json()), version) status = 200 except BadVersionError: log.error('Sync token updated before write for %s...', path) status = 409 return status def _get_multi_raw(self, z, grids): """Gets the raw content and metadata for multiple GridCells from zookeeper. Args: z: zoom level in slippy tile format grids: list of (x,y) tiles to retrieve Returns: content: Combined USS metadata syncs: list of sync tokens in the same order as the grids Raises: IndexError: if it cannot find anything in zookeeper ValueError: if the grid data is not in the right format """ log.debug('Getting multiple grid metadata for %s...', str(grids)) combined_meta = None syncs = [] for x, y in grids: if slippy_util.validate_slippy(z, x, y): (content, metadata) = self._get_raw(z, x, y) if metadata: combined_meta += uss_metadata.USSMetadata(content) syncs.append(metadata.last_modified_transaction_id) else: raise IndexError('Unable to find metadata in platform') else: raise ValueError('Invalid slippy grids for lookup') if len(syncs) == 0: raise IndexError('Unable to find metadata in platform') return combined_meta, syncs def _set_multi_raw(self, z, grids, sync_tokens, uss_id, ws_scope, operation_format, operation_ws, earliest_operation, latest_operation): """Grabs the lock and updates the raw content for multiple GridCells Args: z: zoom level in slippy tile format grids: list of (x,y) tiles to retrieve sync_tokens: list of the sync tokens received during get operation uss_id: plain text identifier for the USS, ws_scope: scope to use to obtain OAuth token, operation_format: output format for operation ws (i.e. NASA, GUTMA), operation_ws: submitting USS endpoint where all flights in this cell can be retrieved from, earliest_operation: lower bound of active or planned flight timestamp, used for quick filtering conflicts. latest_operation: upper bound of active or planned flight timestamp, used for quick filtering conflicts. Raises: IndexError: if it cannot find anything in zookeeper ValueError: if the grid data is not in the right format """ log.debug('Setting multiple grid metadata for %s...', str(grids)) try: contents = [] for i in range(len(grids)): # First, get and update them all in memory, validate the sync_token x = grids[i][0] y = grids[i][1] sync_token = sync_tokens[i] path = '%s/%s/%s/%s/%s' % (GRID_PATH, str(z), str(x), str(y), USS_METADATA_FILE) (content, metadata) = self._get_raw(z, x, y) if str(metadata.last_modified_transaction_id) == str( sync_token): log.debug('Sync_token matches for %d, %d...', x, y) m = uss_metadata.USSMetadata(content) if not m.upsert_operator( uss_id, ws_scope, operation_format, operation_ws, earliest_operation, latest_operation, z, x, y): raise ValueError('Failed to set operator content') contents.append((path, m, metadata.version)) else: log.error( 'Sync token from USS (%s) does not match token from zk (%s)...', str(sync_token), str(metadata.last_modified_transaction_id)) raise KeyError('Composite sync_token has changed') # Now, start a transaction to update them all # the version will catch any changes and roll back any attempted # updates to the grids log.debug('Starting transaction to write all grids at once...') t = self.zk.transaction() for path, m, version in contents: t.set_data(path, json.dumps(m.to_json()), version) log.debug('Committing transaction...') results = t.commit() if isinstance(results[0], RolledBackError): raise KeyError( 'Rolled back multi-grid transaction due to grid change') log.debug('Committed transaction successfully.') except (KeyError, ValueError, IndexError) as e: log.error('Error caught in set_multi_raw %s.', e.message) raise e def _format_status_code_to_jsend(self, status, message=None): """Formats a response based on HTTP status code. Args: status: HTTP status code message: optional message to override preset message for codes Returns: JSend formatted response (https://labs.omniti.com/labs/jsend) """ if status == 200 or status == 204: result = { 'status': 'success', 'code': 204, 'message': 'Empty data set.' } elif status == 400: result = { 'status': 'fail', 'code': status, 'message': 'Parameters are not following the correct format.' } elif status == 404: result = { 'status': 'fail', 'code': status, 'message': 'Unable to pull metadata from lock system.' } elif status == 408: result = { 'status': 'fail', 'code': status, 'message': 'Timeout trying to get lock.' } elif status == 409: result = { 'status': 'fail', 'code': status, 'message': 'Content in metadata has been updated since provided sync token.' } elif status == 424: result = { 'status': 'fail', 'code': status, 'message': 'Content in metadata is not following JSON format guidelines.' } else: result = { 'status': 'fail', 'code': status, 'message': 'Unknown error code occurred.' } if message: result['message'] = message return result @staticmethod def _hash_sync_tokens(syncs): """Hashes a list of sync tokens into a single, positive 64-bit int. For various languages, the limit to integers may be different, therefore we truncate to ensure the hash is the same on all implementations. """ return abs(hash(tuple(sorted(syncs)))) % MAX_SAFE_INTEGER
打印zookeeper树 ''' from kazoo.client import KazooClient from kazoo.client import KazooState import datetime import time hosts = '10.48.57.42:8581,10.48.57.42:8582,10.48.57.42:8583' zk = KazooClient(hosts=hosts) zk.start() print "hosts", hosts print "version", zk.server_version() # # # def printTreeNode(basepath, node, i): if isinstance(node, list): for item in node: # current current_base_path = basepath + "/" + item data, stat = zk.get(current_base_path) ahead = ""
@author: liaoqiqi 打印zookeeper树 """ from kazoo.client import KazooClient import time hosts = '127.0.0.1:3307' zk = KazooClient(hosts=hosts) zk.start() print "hosts", hosts print "version", zk.server_version() # # # def printTreeNode(basepath, node, i): if isinstance(node, list): for item in node: # current current_base_path = basepath + "/" + item data, stat = zk.get(current_base_path) ahead = "" j = 1 while j < i: