class FSProxyAction(CommonAction): """ Generic file system command proxy action class. """ NAME = 'proxy' def __init__(self, fs, action, nodes, debug, comps=None, **kwargs): CommonAction.__init__(self) self.progpath = os.path.abspath(sys.argv[0]) self.fs = fs self.action = action self.nodes = nodes self.debug = debug self._comps = comps self.options = {} for optname in ('addopts', 'failover', 'mountdata', 'fanout', 'dryrun'): self.options[optname] = kwargs.get(optname) self._outputs = MsgTree() self._errpickle = MsgTree() self._silentnodes = NodeSet() # Error nodes without output if self.fs.debug: print("FSProxyAction %s on %s" % (action, nodes)) def info(self): return ActionInfo(self, description='Proxy action') def _prepare_cmd(self): """Create the command line base on proxy properties.""" command = ["%s" % self.progpath] command.append(self.action) command.append("-f %s" % self.fs.fs_name) command.append("-R") if self.debug: command.append("-d") if self._comps: command.append("-l %s" % self._comps.labels()) if self.options['addopts']: command.append("-o '%s'" % self.options['addopts']) if self.options['failover']: command.append("-F '%s'" % self.options['failover']) if self.options['fanout'] is not None: command.append('--fanout=%d' % self.options['fanout']) if self.options['dryrun']: command.append('--dry-run') # To be compatible with older clients in most cases, do not set the # option when it is its default value. if self.options['mountdata'] not in (None, 'auto'): command.append('--mountdata=%s' % self.options['mountdata']) return command def _launch(self): """Launch FS proxy command.""" command = self._prepare_cmd() # Schedule cluster command. self.task.shell(' '.join(command), nodes=self.nodes, handler=self) # Launch events self._actions_start() def _actions_start(self): """ Raise 'proxy' events for all components related to this ProxyAction. """ # Add a 'proxy' running action for each component. if self._comps: for comp in self._comps: # This special event is raised to keep track of undergoing # actions. Maybe this could be dropped is such tracking is no # more needed. comp.action_event(self, 'start') def ev_read(self, worker): node = worker.current_node buf = worker.current_msg try: data = shine_msg_unpack(buf) # COMPAT: Prior to 1.4, 'comp'+'action' was used. # 1.4+ uses ActionInfo if 'comp' in data: action = Action() action.NAME = data.pop('action') comp = data.pop('comp') comp.fs = self.fs desc = "%s of %s" % (action.NAME, comp.longtext()) data['info'] = ActionInfo(action, comp, desc) evtype = 'comp' else: evtype = data.pop('evtype') self.fs.distant_event(evtype, node=node, **data) except ProxyActionUnpickleError as exp: # Maintain a standalone list of unpickling errors. # Node could have unpickling error but still exit with 0 msg = str(exp) if msg not in self._errpickle.get(node, ""): self._errpickle.add(node, msg) except AttributeError as exp: msg = "Cannot read message (check Shine and ClusterShell " \ "version): %s" % str(exp) if msg not in self._errpickle.get(node, ""): self._errpickle.add(node, msg) except ProxyActionUnpackError: # Store output that is not a shine message self._outputs.add(node, buf) def ev_hup(self, worker): """Keep a list of node, without output, with a return code != 0""" # If this node was on error if worker.current_rc != 0: # If there is no known outputs if self._outputs.get(worker.current_node) is None: self._silentnodes.add(worker.current_node) def ev_close(self, worker): """End of proxy command.""" Action.ev_close(self, worker) # Before all, we must check if shine command ran without bugs, node # crash, etc... # So we need to verify all node retcodes and change the component state # on the bad nodes. # Action timed out if worker.did_timeout(): self.set_status(ACT_ERROR) return status = ACT_OK # Remove the 'proxy' running action for each component. if self._comps: for comp in self._comps: # This special event helps to keep track of undergoing actions # (see ev_start()) comp.action_event(self, 'done') comp.sanitize_state(nodes=worker.nodes) # Gather nodes by return code for rc, nodes in worker.iter_retcodes(): # Remote command returns only RUNTIME_ERROR (See RemoteCommand) # some common remote errors: # rc 127 = command not found # rc 126 = found but not executable # rc 1 = python failure... if rc != 0: # If there is at least one error, the action is on error. status = ACT_ERROR # Gather these nodes by buffer key = nodes.__contains__ for buffers, nodes in self._outputs.walk(match=key): # Handle proxy command error nodes = NodeSet.fromlist(nodes) msg = "Remote action %s failed: %s\n" % \ (self.action, buffers) self.fs._handle_shine_proxy_error(nodes, msg) # Raise errors for each unpickling error, # which could happen mostly when Shine exits with 0. for buffers, nodes in self._errpickle.walk(): nodes = NodeSet.fromlist(nodes) self.fs._handle_shine_proxy_error(nodes, str(buffers)) # Raise an error for nodes without output if len(self._silentnodes) > 0: msg = "Remote action %s failed: No response" % self.action self.fs._handle_shine_proxy_error(self._silentnodes, msg) self.set_status(status)
class FSProxyAction(CommonAction): """ Generic file system command proxy action class. """ NAME = 'proxy' def __init__(self, fs, action, nodes, debug, comps=None, **kwargs): CommonAction.__init__(self) self.progpath = os.path.abspath(sys.argv[0]) self.fs = fs self.action = action self.nodes = nodes self.debug = debug self._comps = comps self.options = {} for optname in ('addopts', 'failover', 'mountdata', 'fanout', 'dryrun'): self.options[optname] = kwargs.get(optname) self._outputs = MsgTree() self._errpickle = MsgTree() self._silentnodes = NodeSet() # Error nodes without output if self.fs.debug: print "FSProxyAction %s on %s" % (action, nodes) def info(self): return ActionInfo(self, description='Proxy action') def _prepare_cmd(self): """Create the command line base on proxy properties.""" command = ["%s" % self.progpath] command.append(self.action) command.append("-f %s" % self.fs.fs_name) command.append("-R") if self.debug: command.append("-d") if self._comps: command.append("-l %s" % self._comps.labels()) if self.options['addopts']: command.append("-o '%s'" % self.options['addopts']) if self.options['failover']: command.append("-F '%s'" % self.options['failover']) if self.options['fanout'] is not None: command.append('--fanout=%d' % self.options['fanout']) if self.options['dryrun']: command.append('--dry-run') # To be compatible with older clients in most cases, do not set the # option when it is its default value. if self.options['mountdata'] not in (None, 'auto'): command.append('--mountdata=%s' % self.options['mountdata']) return command def _launch(self): """Launch FS proxy command.""" command = self._prepare_cmd() # Schedule cluster command. self.task.shell(' '.join(command), nodes=self.nodes, handler=self) # Launch events self._actions_start() def _actions_start(self): """ Raise 'proxy' events for all components related to this ProxyAction. """ # Add a 'proxy' running action for each component. if self._comps: for comp in self._comps: # This special event is raised to keep track of undergoing # actions. Maybe this could be dropped is such tracking is no # more needed. comp.action_event(self, 'start') def ev_read(self, worker): node = worker.current_node buf = worker.current_msg try: data = shine_msg_unpack(buf) # COMPAT: Prior to 1.4, 'comp'+'action' was used. # 1.4+ uses ActionInfo if 'comp' in data: action = Action() action.NAME = data.pop('action') comp = data.pop('comp') comp.fs = self.fs desc = "%s of %s" % (action.NAME, comp.longtext()) data['info'] = ActionInfo(action, comp, desc) evtype = 'comp' else: evtype = data.pop('evtype') self.fs.distant_event(evtype, node=node, **data) except ProxyActionUnpickleError, exp: # Maintain a standalone list of unpickling errors. # Node could have unpickling error but still exit with 0 msg = str(exp) if msg not in self._errpickle.get(node, ""): self._errpickle.add(node, msg) except AttributeError, exp: msg = "Cannot read message (check Shine and ClusterShell " \ "version): %s" % str(exp) if msg not in self._errpickle.get(node, ""): self._errpickle.add(node, msg)
class FSProxyAction(CommonAction): """ Generic file system command proxy action class. """ NAME = 'proxy' def __init__(self, fs, action, nodes, debug, comps=None, addopts=None, failover=None, mountdata=None): CommonAction.__init__(self) self.progpath = os.path.abspath(sys.argv[0]) self.fs = fs self.action = action self.nodes = nodes self.debug = debug self._comps = comps self.addopts = addopts self.failover = failover self.mountdata = mountdata self._outputs = MsgTree() self._errpickle = MsgTree() self._silentnodes = NodeSet() # Error nodes without output if self.fs.debug: print "FSProxyAction %s on %s" % (action, nodes) def _prepare_cmd(self): """Create the command line base on proxy properties.""" command = ["%s" % self.progpath] command.append(self.action) command.append("-f %s" % self.fs.fs_name) command.append("-R") if self.debug: command.append("-d") if self._comps: command.append("-l %s" % self._comps.labels()) if self.addopts: command.append("-o '%s'" % self.addopts) if self.failover: command.append("-F '%s'" % self.failover) # To be compatible with older clients in most cases, do not set the # option when it is its default value. if self.mountdata is not None and self.mountdata != 'auto': command.append('--mountdata=%s' % self.mountdata) return command def _launch(self): """Launch FS proxy command.""" command = self._prepare_cmd() # Schedule cluster command. self.task.shell(' '.join(command), nodes=self.nodes, handler=self) # Launch events self._actions_start() def _actions_start(self): """ Raise 'proxy' events for all components related to this ProxyAction. """ # Add a 'proxy' running action for each component. if self._comps: for comp in self._comps: # Warning: there is no clean call at the end of the action. # cleaning is done by hand. comp.action_start('proxy') def ev_read(self, worker): node = worker.current_node buf = worker.current_msg try: data = shine_msg_unpack(buf) compname = data.pop('compname') action = data.pop('action') status = data.pop('status') self.fs.distant_event(compname, action, status, node=node, **data) except ProxyActionUnpickleError, exp: # Maintain a standalone list of unpickling errors. # Node could have unpickling error but still exit with 0 msg = str(exp) if msg not in self._errpickle.get(node, ""): self._errpickle.add(node, msg) except AttributeError, exp: msg = "Cannot read message (check Shine and ClusterShell " \ "version): %s" % str(exp) if msg not in self._errpickle.get(node, ""): self._errpickle.add(node, msg)