def __init__(self, seed_nodes, use_telnet=False, user=None, password=None): """ Want to be able to support multiple nodes on one box (for testing) seed_nodes should be the form (address,port) address can be fqdn or ip. """ self.__dict__ = self.cluster_state if self.cluster_state != {}: return # will we connect using telnet port? self.use_telnet = use_telnet self.user = user self.password = password # self.nodes is a dict from Node ID -> Node objects self.nodes = {} # self.node_lookup is a dict of (fqdn, port) -> Node # and (ip, port) -> Node, and node.node_id -> Node self.node_lookup = PrefixDict() self._original_seed_nodes = set(seed_nodes) self._seed_nodes = set(seed_nodes) self._live_nodes = set() # crawl the cluster search for nodes in addition to the seed nodes. self._enable_crawler = True self._crawl()
def _initCommands(self): command_re = re.compile("^(do_(.*))$") commands = map(lambda v: command_re.match(v).groups(), filter(command_re.search, dir(self))) self.commands = PrefixDict() for command in commands: self.commands.add(command[1], getattr(self, command[0])) for command, controller in self.controller_map.items(): try: controller = controller() except: pass self.commands.add(command, controller)
def setUp(self): self.cluster_patch = patch('lib.cluster.Cluster') #self.view_patch = patch('lib.view.CliView') real_stdoup = sys.stdout sys.stdout = StringIO() self.addCleanup(patch.stopall) self.addCleanup(reset_stdout) self.MockCluster = self.cluster_patch.start() #self.MockView = self.view_patch.start() Cluster._crawl = classmethod(lambda self: None) Cluster._callNodeMethod = classmethod( lambda self, nodes, method_name, *args, **kwargs: {"test":IOError("test error")}) n = Node("172.99.99.99") Cluster.getNode = classmethod( lambda self, key: [n]) pd = PrefixDict() pd['test'] = 'test' Cluster.getPrefixes = classmethod(lambda self: pd) self.rc = RootController()
def setUp(self): self.test_dict = t = PrefixDict() t['u01.citrusleaf.local'] = 1 t['u02.citrusleaf.local'] = 2 t['u11.citrusleaf.local'] = 3 t['u12'] = 4
class BaseController(object): view = None cluster = None def __init__(self, seed_nodes=[('127.0.0.1',3000)] , use_telnet=False, user=None, password=None): cls = BaseController cls.view = view.CliView() cls.cluster = Cluster(seed_nodes, use_telnet, user, password) # instance vars self.modifiers = set() def _initCommands(self): command_re = re.compile("^(do_(.*))$") commands = map(lambda v: command_re.match(v).groups() , filter(command_re.search, dir(self))) self.commands = PrefixDict() for command in commands: self.commands.add(command[1], getattr(self, command[0])) for command, controller in self.controller_map.items(): try: controller = controller() except: pass self.commands.add(command, controller) def complete(self, line): self._init() command = line.pop(0) if line else '' commands = self.commands.getKey(command) if command != commands[0] and len(line) == 0: # if user has full command name and is hitting tab, # the user probably wants the next level return sorted(commands) try: return self.controller_map[commands[0]]().complete(line) except: # The line contains an ambiguous entry # or exact match return [] def __call__(self, line): return self.execute(line) def _initControllerMap(self): try: # define controller map if not defined if self.controller_map: pass except: self.controller_map = {} def _init(self): self._initControllerMap() self._initCommands() def _findMethod(self, line): method = None try: command = line.pop(0) except IndexError: # Popped last element use default method = getattr(self, DEFAULT) if method is None: try: method = self.commands[command] if len(method) > 1: # handle ambiguos commands commands = sorted(self.commands.getKey(command)) commands[-1] = "or %s"%(commands[-1]) if len(commands) > 2: commands = ', '.join(commands) else: commands = ' '.join(commands) raise ShellException( "Ambiguous command: '%s' may be %s."%(command , commands)) else: method = method[0] except KeyError: line.insert(0, command) # Maybe the controller understands the command from here # Have to forward it to the controller since some commands # may have strange options like asinfo method = getattr(self, DEFAULT) return method def execute(self, line): self._init() method = self._findMethod(line) if method: try: if inspect.ismethod(method): self.preCommand(line[:]) return method(line) except IOError as e: raise ShellException(str(e)) else: raise ShellException( "Method was not set? %s"%(line)) def executeHelp(self, line, indent=0): self._init() method = self._findMethod(line) if method: try: try: method_name = method.__name__ except: #method_name = method.__class__.__name__ method_name = None if method_name == DEFAULT: # Print controller help CommandHelp.display(self, indent=indent) if self.modifiers: CommandHelp.printText( "%sModifiers%s: %s"%(terminal.underline() , terminal.reset() , ", ".join( sorted(self.modifiers))) , indent=indent) if CommandHelp.hasHelp(method): CommandHelp.display(method, indent=indent) indent += 2 for command in sorted(self.commands.keys()): CommandHelp.printText("- %s%s%s:"%(terminal.bold() , command , terminal.reset()) , indent=indent-1) self.executeHelp([command], indent=indent) return elif isinstance(method, ShellException): # Method not implemented pass elif method_name is None: # Nothing to print yet method.executeHelp(line, indent=indent) else: # Print help for a command CommandHelp.display(method, indent=indent) return except IOError as e: raise ShellException(str(e)) else: raise ShellException( "Method was not set? %s"%(line)) def _do_default(self, line): # Override method to provide default command behavior raise ShellException("Do not understand '%s'"%(" ".join(line))) def preCommand(self, line): pass # Hook to be defined by subclasses
class BaseController(object): view = None cluster = None def __init__(self, seed_nodes=[('127.0.0.1', 3000)], use_telnet=False, user=None, password=None): cls = BaseController cls.view = view.CliView() cls.cluster = Cluster(seed_nodes, use_telnet, user, password) # instance vars self.modifiers = set() def _initCommands(self): command_re = re.compile("^(do_(.*))$") commands = map(lambda v: command_re.match(v).groups(), filter(command_re.search, dir(self))) self.commands = PrefixDict() for command in commands: self.commands.add(command[1], getattr(self, command[0])) for command, controller in self.controller_map.items(): try: controller = controller() except: pass self.commands.add(command, controller) def complete(self, line): self._init() command = line.pop(0) if line else '' commands = self.commands.getKey(command) if command != commands[0] and len(line) == 0: # if user has full command name and is hitting tab, # the user probably wants the next level return sorted(commands) try: return self.controller_map[commands[0]]().complete(line) except: # The line contains an ambiguous entry # or exact match return [] def __call__(self, line): return self.execute(line) def _initControllerMap(self): try: # define controller map if not defined if self.controller_map: pass except: self.controller_map = {} def _init(self): self._initControllerMap() self._initCommands() def _findMethod(self, line): method = None try: command = line.pop(0) except IndexError: # Popped last element use default method = getattr(self, DEFAULT) if method is None: try: method = self.commands[command] if len(method) > 1: # handle ambiguos commands commands = sorted(self.commands.getKey(command)) commands[-1] = "or %s" % (commands[-1]) if len(commands) > 2: commands = ', '.join(commands) else: commands = ' '.join(commands) raise ShellException("Ambiguous command: '%s' may be %s." % (command, commands)) else: method = method[0] except KeyError: line.insert(0, command) # Maybe the controller understands the command from here # Have to forward it to the controller since some commands # may have strange options like asinfo method = getattr(self, DEFAULT) return method def execute(self, line): self._init() method = self._findMethod(line) if method: try: if inspect.ismethod(method): self.preCommand(line[:]) return method(line) except IOError as e: raise ShellException(str(e)) else: raise ShellException("Method was not set? %s" % (line)) def executeHelp(self, line, indent=0): self._init() method = self._findMethod(line) if method: try: try: method_name = method.__name__ except: #method_name = method.__class__.__name__ method_name = None if method_name == DEFAULT: # Print controller help CommandHelp.display(self, indent=indent) if self.modifiers: CommandHelp.printText( "%sModifiers%s: %s" % (terminal.underline(), terminal.reset(), ", ".join( sorted(self.modifiers))), indent=indent) if CommandHelp.hasHelp(method): CommandHelp.display(method, indent=indent) indent += 2 for command in sorted(self.commands.keys()): CommandHelp.printText( "- %s%s%s:" % (terminal.bold(), command, terminal.reset()), indent=indent - 1) self.executeHelp([command], indent=indent) return elif isinstance(method, ShellException): # Method not implemented pass elif method_name is None: # Nothing to print yet method.executeHelp(line, indent=indent) else: # Print help for a command CommandHelp.display(method, indent=indent) return except IOError as e: raise ShellException(str(e)) else: raise ShellException("Method was not set? %s" % (line)) def _do_default(self, line): # Override method to provide default command behavior raise ShellException("Do not understand '%s'" % (" ".join(line))) def preCommand(self, line): pass # Hook to be defined by subclasses
class Cluster(object): # Kinda like a singleton... All instantiated classes will share the same # state... This makes the class no cluster_state = {} def __init__(self, seed_nodes, use_telnet=False, user=None, password=None): """ Want to be able to support multiple nodes on one box (for testing) seed_nodes should be the form (address,port) address can be fqdn or ip. """ self.__dict__ = self.cluster_state if self.cluster_state != {}: return # will we connect using telnet port? self.use_telnet = use_telnet self.user = user self.password = password # self.nodes is a dict from Node ID -> Node objects self.nodes = {} # self.node_lookup is a dict of (fqdn, port) -> Node # and (ip, port) -> Node, and node.node_id -> Node self.node_lookup = PrefixDict() self._original_seed_nodes = set(seed_nodes) self._seed_nodes = set(seed_nodes) self._live_nodes = set() # crawl the cluster search for nodes in addition to the seed nodes. self._enable_crawler = True self._crawl() def __str__(self): nodes = self.nodes.values() online = [n.key for n in filter(lambda n: n.alive, nodes)] offline = [n.key for n in filter(lambda n: not n.alive, nodes)] retval = "Found %s nodes"%(len(nodes)) if online: retval += "\nOnline: %s"%(", ".join(online)) if offline: retval += "\nOffline: %s"%(", ".join(offline)) return retval def getPrefixes(self): prefixes = {} for node_key, node in self.nodes.iteritems(): fqdn = node.sockName(use_fqdn=True) prefixes[node_key] = self.node_lookup.getPrefix(fqdn) return prefixes def getExpectedPrincipal(self): try: return max([n.node_id for n in self.nodes.itervalues()]) except: return '' def getVisibility(self): return self._live_nodes def _shouldCrawl(self): """ Determine if we need to do a crawl. We crawl if the union of all services lists is not equal to the set of nodes that this tool percieves as alive. """ if not self._enable_crawler: return False self._enable_crawler = False current_services = set() self._refreshNodeLiveliness() try: infoservices = self.infoServices().values() for s in self.infoServices().values(): if isinstance(s, Exception): continue current_services |= set(s) if current_services and current_services == self._live_nodes: # services have not changed, do not crawl # if services are empty they crawl regardless return False else: # services have changed return True except IOError: # We aren't connected yet, definently crawl. return True finally: # Re-enable crawler before exiting self._enable_crawler = True def _crawl(self): """ Find all the nodes in the cluster and add them to self.nodes. """ if not self._shouldCrawl(): return self._enable_crawler = False try: if self._seed_nodes: seed_nodes = self._seed_nodes else: seed_nodes = self._original_seed_nodes # clear the current lookup and node list all_services = set() visited = set() unvisited = set(seed_nodes) while unvisited - visited: l_unvisited = list(unvisited) nodes = util.concurrent_map(self._registerNode, l_unvisited) live_nodes = filter( lambda n: n is not None and n.alive and n not in visited , nodes) visited |= unvisited unvisited.clear() services_list = util.concurrent_map(self._getServices, live_nodes) for node, services in zip(live_nodes, services_list): if isinstance(services, Exception): continue all_services.update(set(services)) all_services.add((node.ip, node.port)) unvisited = all_services - visited if all_services: self._seed_nodes = all_services self._refreshNodeLiveliness() except: pass finally: self._enable_crawler = True def _refreshNodeLiveliness(self): live_nodes = filter(lambda n: n.alive, self.nodes.itervalues()) self._live_nodes.clear() self._live_nodes.update(map(lambda n: (n.ip, n.port), live_nodes)) def updateNode(self, node): self.nodes[node.key] = node # add node to lookup self.node_lookup[node.sockName(use_fqdn = True)] = node self.node_lookup[node.sockName()] = node if node.alive: self.node_lookup[node.node_id] = node def getNode(self, node): return self.node_lookup[node] def _registerNode(self, ip_port): """ Instantiate and return a new node If cannot instantiate node, return None. Creates a new node if: 1) node.key doesn't already exist 2) node.key exists but existing node is not alive """ try: ip, port = ip_port except Exception as e: print "ip_port is expected to be a tuple of len 2, " + \ "instead it is of type %s and str value of %s"%(type(ip_port) , str(ip_port)) return None try: node_key = Node.createKey(ip, port) if node_key in self.nodes: existing = self.nodes[node_key] else: existing = None if not existing or not existing.alive: new_node = Node(ip , port , use_telnet=self.use_telnet , user=self.user , password=self.password) if existing and not new_node.alive: new_node = existing self.updateNode(new_node) return new_node except: return None def _getServices(self, node): """ Given a node object return its services list """ services = node.infoServicesAlumni() if services: return services return node.infoServices() # compatible for version without alumni def _callNodeMethod(self, nodes, method_name, *args, **kwargs): """ Run a particular method command across a set of nodes nodes is a list of nodes to to run the command against. if nodes is None then we run on all nodes. """ self._crawl() if nodes == 'all': use_nodes = self.nodes.values() elif type(nodes) == list: use_nodes = [] for n in nodes: try: node_list = self.getNode(n) if isinstance(node_list, list): use_nodes.extend(self.getNode(n)) else: use_nodes.append(self.getNode(n)) except: # Ignore ambiguous and key errors continue else: raise TypeError( "nodes should be 'all' or list found %s"%type(nodes)) if len(use_nodes) == 0: raise IOError('Unable to find any Aerospike nodes') return dict( util.concurrent_map( lambda node: (node.key, getattr(node, method_name)(*args, **kwargs)), use_nodes)) def isXDREnabled(self, nodes = 'all'): return self._callNodeMethod(nodes, 'isXDREnabled') def __getattr__(self, name): regex = re.compile("^info.*$|^xdr.*$") if regex.match(name): def infoFunc(*args, **kwargs): if 'nodes' not in kwargs: nodes = 'all' else: nodes = kwargs['nodes'] del(kwargs['nodes']) return self._callNodeMethod(nodes, name, *args, **kwargs) return infoFunc else: raise AttributeError("Cluster has not attribute '%s'"%(name))
class Cluster(object): # Kinda like a singleton... All instantiated classes will share the same # state... This makes the class no cluster_state = {} def __init__(self, seed_nodes, use_telnet=False, user=None, password=None): """ Want to be able to support multiple nodes on one box (for testing) seed_nodes should be the form (address,port) address can be fqdn or ip. """ self.__dict__ = self.cluster_state if self.cluster_state != {}: return # will we connect using telnet port? self.use_telnet = use_telnet self.user = user self.password = password # self.nodes is a dict from Node ID -> Node objects self.nodes = {} # self.node_lookup is a dict of (fqdn, port) -> Node # and (ip, port) -> Node, and node.node_id -> Node self.node_lookup = PrefixDict() self._original_seed_nodes = set(seed_nodes) self._seed_nodes = set(seed_nodes) self._live_nodes = set() # crawl the cluster search for nodes in addition to the seed nodes. self._enable_crawler = True self._crawl() def __str__(self): nodes = self.nodes.values() online = [n.key for n in filter(lambda n: n.alive, nodes)] offline = [n.key for n in filter(lambda n: not n.alive, nodes)] retval = "Found %s nodes" % (len(nodes)) if online: retval += "\nOnline: %s" % (", ".join(online)) if offline: retval += "\nOffline: %s" % (", ".join(offline)) return retval def getPrefixes(self): prefixes = {} for node_key, node in self.nodes.iteritems(): fqdn = node.sockName(use_fqdn=True) prefixes[node_key] = self.node_lookup.getPrefix(fqdn) return prefixes def getExpectedPrincipal(self): try: return max([n.node_id for n in self.nodes.itervalues()]) except: return '' def getVisibility(self): return self._live_nodes def _shouldCrawl(self): """ Determine if we need to do a crawl. We crawl if the union of all services lists is not equal to the set of nodes that this tool percieves as alive. """ if not self._enable_crawler: return False self._enable_crawler = False current_services = set() self._refreshNodeLiveliness() try: infoservices = self.infoServices().values() for s in self.infoServices().values(): if isinstance(s, Exception): continue current_services |= set(s) if current_services and current_services == self._live_nodes: # services have not changed, do not crawl # if services are empty they crawl regardless return False else: # services have changed return True except IOError: # We aren't connected yet, definently crawl. return True finally: # Re-enable crawler before exiting self._enable_crawler = True def _crawl(self): """ Find all the nodes in the cluster and add them to self.nodes. """ if not self._shouldCrawl(): return self._enable_crawler = False try: if self._seed_nodes: seed_nodes = self._seed_nodes else: seed_nodes = self._original_seed_nodes # clear the current lookup and node list all_services = set() visited = set() unvisited = set(seed_nodes) while unvisited - visited: l_unvisited = list(unvisited) nodes = util.concurrent_map(self._registerNode, l_unvisited) live_nodes = filter( lambda n: n is not None and n.alive and n not in visited, nodes) visited |= unvisited unvisited.clear() services_list = util.concurrent_map(self._getServices, live_nodes) for node, services in zip(live_nodes, services_list): if isinstance(services, Exception): continue all_services.update(set(services)) all_services.add((node.ip, node.port)) unvisited = all_services - visited if all_services: self._seed_nodes = all_services self._refreshNodeLiveliness() except: pass finally: self._enable_crawler = True def _refreshNodeLiveliness(self): live_nodes = filter(lambda n: n.alive, self.nodes.itervalues()) self._live_nodes.clear() self._live_nodes.update(map(lambda n: (n.ip, n.port), live_nodes)) def updateNode(self, node): self.nodes[node.key] = node # add node to lookup self.node_lookup[node.sockName(use_fqdn=True)] = node self.node_lookup[node.sockName()] = node if node.alive: self.node_lookup[node.node_id] = node def getNode(self, node): return self.node_lookup[node] def _registerNode(self, ip_port): """ Instantiate and return a new node If cannot instantiate node, return None. Creates a new node if: 1) node.key doesn't already exist 2) node.key exists but existing node is not alive """ try: ip, port = ip_port except Exception as e: print "ip_port is expected to be a tuple of len 2, " + \ "instead it is of type %s and str value of %s"%(type(ip_port) , str(ip_port)) return None try: node_key = Node.createKey(ip, port) if node_key in self.nodes: existing = self.nodes[node_key] else: existing = None if not existing or not existing.alive: new_node = Node(ip, port, use_telnet=self.use_telnet, user=self.user, password=self.password) if existing and not new_node.alive: new_node = existing self.updateNode(new_node) return new_node except: return None def _getServices(self, node): """ Given a node object return its services list """ services = node.infoServicesAlumni() if services: return services return node.infoServices() # compatible for version without alumni def _callNodeMethod(self, nodes, method_name, *args, **kwargs): """ Run a particular method command across a set of nodes nodes is a list of nodes to to run the command against. if nodes is None then we run on all nodes. """ self._crawl() if nodes == 'all': use_nodes = self.nodes.values() elif type(nodes) == list: use_nodes = [] for n in nodes: try: node_list = self.getNode(n) if isinstance(node_list, list): use_nodes.extend(self.getNode(n)) else: use_nodes.append(self.getNode(n)) except: # Ignore ambiguous and key errors continue else: raise TypeError("nodes should be 'all' or list found %s" % type(nodes)) if len(use_nodes) == 0: raise IOError('Unable to find any Aerospike nodes') return dict( util.concurrent_map( lambda node: (node.key, getattr(node, method_name) (*args, **kwargs)), use_nodes)) def isXDREnabled(self, nodes='all'): return self._callNodeMethod(nodes, 'isXDREnabled') def __getattr__(self, name): regex = re.compile("^info.*$|^xdr.*$") if regex.match(name): def infoFunc(*args, **kwargs): if 'nodes' not in kwargs: nodes = 'all' else: nodes = kwargs['nodes'] del (kwargs['nodes']) return self._callNodeMethod(nodes, name, *args, **kwargs) return infoFunc else: raise AttributeError("Cluster has not attribute '%s'" % (name))