ansible 2.5--2.7API的Callback回调源码剖析
重写回调方法:
class ResultCallback(CallbackBase): #继承callbackbase,并重写其3个方法 """A sample callback plugin used for performing an action as results come in If you want to collect all results into a single object for processing at the end of the execution, look into utilizing the ``json`` callback plugin or writing your own custom callback plugin """ def v2_runner_on_ok(self, result, **kwargs): #定义成功 """Print a json representation of the result This method could store the result in an instance attribute for retrieval later """ # self.host_ok[result._host.get_name()] = result host = result._host currt_tiem = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') logs = json.dumps({currt_tiem: {host.name: result._result['msg']}}, indent=4) with open('/opt/aaaa.json', 'a') as f: f.write(logs+'\n') self.result_log = result._result['msg'] def v2_runner_on_unreachable(self, result, **kwargs): """Print a json representation of the result This method could store the result in an instance attribute for retrieval later """ # self.host_ok[result._host.get_name()] = result host = result._host currt_tiem = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.result_log = result._result['msg'] logs = json.dumps({currt_tiem: {host.name: self.result_log}}, indent=4) with open('/opt/aaaa.json', 'a') as f: f.write(logs + '\n') def v2_runner_on_failed(self, result, **kwargs): """Print a json representation of the result This method could store the result in an instance attribute for retrieval later """ host = result._host currt_tiem = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.result_log = result._result['msg'] logs = json.dumps({currt_tiem: {host.name: self.result_log}}, indent=4) with open('/opt/aaaa.json', 'a') as f: f.write(logs + '\n')
ansible api调用主程序
1 class Run_comm(): 2 def __init__(self,server_ip): 3 Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 'check', 'diff']) 4 self.options = Options(connection='smart', module_path=['/usr/local/lib/python2.7/site-packages/ansible/modules'], 5 forks=10, become=None, become_method='su', become_user='root', check=False, diff=False) 6 7 self.loader = DataLoader() 8 self.passwords = dict(vault_pass='vagrant') 9 10 self.results_callback = ResultCallback() #把回调类实例化 11 12 self.inventory = InventoryManager(loader=self.loader, sources='{},'.format(server_ip)) 13 14 self.variable_manager = VariableManager(loader=self.loader, inventory=self.inventory) 15 # self.extra_vars ={'ansible_ssh_user': 'root','ansible_ssh_pass': 'aslan-game@2017~johnly'} 16 # self.extra_vars = {'ansible_ssh_user': 'root', 'ansible_ssh_pass': 'vagrant'} 17 # self.extra_vars = {'ansible_ssh_user': 'root', 'ansible_ssh_pass': 'ysdon2016'} 18 self.extra_vars = {'ansible_ssh_user': ssh_dict['user'], 'ansible_ssh_pass': ssh_dict['password']} 19 self.variable_manager.extra_vars = self.extra_vars 20 self.tqm = None 21 22 23 def run_command(self,command): 24 self.play_source = dict( 25 name="Ansible Play", 26 hosts='all', 27 gather_facts='yes', 28 tasks=[ 29 dict(action=dict(module='shell', args=command), register='shell_out'), 30 dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}'))) 31 ] 32 ) 33 self.play = Play().load(self.play_source, variable_manager=self.variable_manager, loader=self.loader) 34 try: 35 36 self.tqm = TaskQueueManager( 37 inventory=self.inventory, 38 variable_manager=self.variable_manager, 39 loader=self.loader, 40 options=self.options, 41 passwords=self.passwords, 42 stdout_callback=self.results_callback, #把实例化的回调类传入 43 ) 44 result = self.tqm.run(self.play) 45 finally: 46 47 if self.tqm is not None: 48 self.tqm.cleanup() 49 return self.results_callback.result_log
TaskQueueManager中调用过程:在run中加载所有的回调类,然后把play事件设置进回调类中,并执行v2_playbook_on_play_start回调方法,表示要开始执行play操作
1 class TaskQueueManager: 2 3 ''' 4 This class handles the multiprocessing requirements of Ansible by 5 creating a pool of worker forks, a result handler fork, and a 6 manager object with shared datastructures/queues for coordinating 7 work between all processes. 8 9 The queue manager is responsible for loading the play strategy plugin, 10 which dispatches the Play's tasks to hosts. 11 ''' 12 13 RUN_OK = 0 14 RUN_ERROR = 1 15 RUN_FAILED_HOSTS = 2 16 RUN_UNREACHABLE_HOSTS = 4 17 RUN_FAILED_BREAK_PLAY = 8 18 RUN_UNKNOWN_ERROR = 255 19 20 def __init__(self, inventory, variable_manager, loader, options, passwords, stdout_callback=None, run_additional_callbacks=True, run_tree=False): 21 22 self._inventory = inventory 23 self._variable_manager = variable_manager 24 self._loader = loader 25 self._options = options 26 self._stats = AggregateStats() 27 self.passwords = passwords 28 self._stdout_callback = stdout_callback #赋值 29 self._run_additional_callbacks = run_additional_callbacks 30 self._run_tree = run_tree 31 32 self._callbacks_loaded = False 33 self._callback_plugins = [] 34 self._start_at_done = False 35 36 # make sure any module paths (if specified) are added to the module_loader 37 if options.module_path: 38 for path in options.module_path: 39 if path: 40 module_loader.add_directory(path) 41 42 # a special flag to help us exit cleanly 43 self._terminated = False 44 45 # this dictionary is used to keep track of notified handlers 46 self._notified_handlers = dict() 47 self._listening_handlers = dict() 48 49 # dictionaries to keep track of failed/unreachable hosts 50 self._failed_hosts = dict() 51 self._unreachable_hosts = dict() 52 53 try: 54 self._final_q = multiprocessing.Queue() 55 except OSError as e: 56 raise AnsibleError("Unable to use multiprocessing, this is normally caused by lack of access to /dev/shm: %s" % to_native(e)) 57 58 # A temporary file (opened pre-fork) used by connection 59 # plugins for inter-process locking. 60 self._connection_lockfile = tempfile.TemporaryFile() 61 def run(self, play): 62 ''' 63 Iterates over the roles/tasks in a play, using the given (or default) 64 strategy for queueing tasks. The default is the linear strategy, which 65 operates like classic Ansible by keeping all hosts in lock-step with 66 a given task (meaning no hosts move on to the next task until all hosts 67 are done with the current task). 68 ''' 69 70 if not self._callbacks_loaded: 71 self.load_callbacks() #在run中调用:加载回调类 72 73 all_vars = self._variable_manager.get_vars(play=play) 74 warn_if_reserved(all_vars) 75 templar = Templar(loader=self._loader, variables=all_vars) 76 77 new_play = play.copy() 78 new_play.post_validate(templar) 79 new_play.handlers = new_play.compile_roles_handlers() + new_play.handlers 80 81 self.hostvars = HostVars( 82 inventory=self._inventory, 83 variable_manager=self._variable_manager, 84 loader=self._loader, 85 ) 86 87 play_context = PlayContext(new_play, self._options, self.passwords, self._connection_lockfile.fileno()) 88 for callback_plugin in self._callback_plugins: #循环所有回调类 89 if hasattr(callback_plugin, 'set_play_context'): 90 callback_plugin.set_play_context(play_context) #把要回调的play操作设置进回调类里面 91 92 self.send_callback('v2_playbook_on_play_start', new_play) #执行v2_playbook_on_play_start回调方法 93 94 # initialize the shared dictionary containing the notified handlers 95 self._initialize_notified_handlers(new_play) 96 97 # build the iterator 98 iterator = PlayIterator( 99 inventory=self._inventory, 100 play=new_play, 101 play_context=play_context, 102 variable_manager=self._variable_manager, 103 all_vars=all_vars, 104 start_at_done=self._start_at_done, 105 ) 106 107 # adjust to # of workers to configured forks or size of batch, whatever is lower 108 self._initialize_processes(min(self._options.forks, iterator.batch_size)) 109 110 # load the specified strategy (or the default linear one) 111 strategy = strategy_loader.get(new_play.strategy, self) 112 if strategy is None: 113 raise AnsibleError("Invalid play strategy specified: %s" % new_play.strategy, obj=play._ds) 114 115 # Because the TQM may survive multiple play runs, we start by marking 116 # any hosts as failed in the iterator here which may have been marked 117 # as failed in previous runs. Then we clear the internal list of failed 118 # hosts so we know what failed this round. 119 for host_name in self._failed_hosts.keys(): 120 host = self._inventory.get_host(host_name) 121 iterator.mark_host_failed(host) 122 123 self.clear_failed_hosts() 124 125 # during initialization, the PlayContext will clear the start_at_task 126 # field to signal that a matching task was found, so check that here 127 # and remember it so we don't try to skip tasks on future plays 128 if getattr(self._options, 'start_at_task', None) is not None and play_context.start_at_task is None: 129 self._start_at_done = True 130 131 # and run the play using the strategy and cleanup on way out 132 play_return = strategy.run(iterator, play_context) 133 134 # now re-save the hosts that failed from the iterator to our internal list 135 for host_name in iterator.get_failed_hosts(): 136 self._failed_hosts[host_name] = True 137 138 strategy.cleanup() 139 self._cleanup_processes() 140 return play_return 141 def load_callbacks(self): 142 ''' 143 Loads all available callbacks, with the exception of those which 144 utilize the CALLBACK_TYPE option. When CALLBACK_TYPE is set to 'stdout', 145 only one such callback plugin will be loaded. 146 ''' 147 148 if self._callbacks_loaded: 149 return 150 151 stdout_callback_loaded = False 152 if self._stdout_callback is None: 153 self._stdout_callback = C.DEFAULT_STDOUT_CALLBACK 154 155 if isinstance(self._stdout_callback, CallbackBase): #判断传入的回调类是不是CallBack的子类 156 stdout_callback_loaded = True 157 elif isinstance(self._stdout_callback, string_types): 158 if self._stdout_callback not in callback_loader: 159 raise AnsibleError("Invalid callback for stdout specified: %s" % self._stdout_callback) 160 else: 161 self._stdout_callback = callback_loader.get(self._stdout_callback) 162 try: 163 self._stdout_callback.set_options() #设置 164 except AttributeError: 165 display.deprecated("%s stdout callback, does not support setting 'options', it will work for now, " 166 " but this will be required in the future and should be updated," 167 " see the 2.4 porting guide for details." % self._stdout_callback._load_name, version="2.9") 168 stdout_callback_loaded = True 169 else: 170 raise AnsibleError("callback must be an instance of CallbackBase or the name of a callback plugin") 171 172 for callback_plugin in callback_loader.all(class_only=True): #循环所有回调类 173 callback_type = getattr(callback_plugin, 'CALLBACK_TYPE', '') 174 callback_needs_whitelist = getattr(callback_plugin, 'CALLBACK_NEEDS_WHITELIST', False) 175 (callback_name, _) = os.path.splitext(os.path.basename(callback_plugin._original_path)) 176 if callback_type == 'stdout': 177 # we only allow one callback of type 'stdout' to be loaded, 178 if callback_name != self._stdout_callback or stdout_callback_loaded: 179 continue 180 stdout_callback_loaded = True 181 elif callback_name == 'tree' and self._run_tree: 182 # special case for ansible cli option 183 pass 184 elif not self._run_additional_callbacks or (callback_needs_whitelist and ( 185 C.DEFAULT_CALLBACK_WHITELIST is None or callback_name not in C.DEFAULT_CALLBACK_WHITELIST)): 186 # 2.x plugins shipped with ansible should require whitelisting, older or non shipped should load automatically 187 continue 188 189 callback_obj = callback_plugin() 190 try: 191 callback_obj.set_options() 192 except AttributeError: 193 display.deprecated("%s callback, does not support setting 'options', it will work for now, " 194 " but this will be required in the future and should be updated, " 195 " see the 2.4 porting guide for details." % callback_obj._load_name, version="2.9") 196 self._callback_plugins.append(callback_obj) #把合适的回调类添加进列表 197 198 self._callbacks_loaded = True
下面这个方法是执行回调方法
1 def send_callback(self, method_name, *args, **kwargs): 2 for callback_plugin in [self._stdout_callback] + self._callback_plugins: 3 # a plugin that set self.disabled to True will not be called 4 # see osx_say.py example for such a plugin 5 if getattr(callback_plugin, 'disabled', False): 6 continue 7 8 # try to find v2 method, fallback to v1 method, ignore callback if no method found 9 methods = [] 10 for possible in [method_name, 'v2_on_any']: 11 gotit = getattr(callback_plugin, possible, None) 12 if gotit is None: 13 gotit = getattr(callback_plugin, possible.replace('v2_', ''), None) #在这里获取要回调的方法的对象 14 if gotit is not None: 15 methods.append(gotit) #添加进列表 16 17 # send clean copies 18 new_args = [] 19 for arg in args: 20 # FIXME: add play/task cleaners 21 if isinstance(arg, TaskResult): 22 new_args.append(arg.clean_copy()) 23 # elif isinstance(arg, Play): 24 # elif isinstance(arg, Task): 25 else: 26 new_args.append(arg) 27 28 for method in methods: 29 try: 30 method(*new_args, **kwargs) #执行回调方法 31 except Exception as e: 32 # TODO: add config toggle to make this fatal or not? 33 display.warning(u"Failure using method (%s) in callback plugin (%s): %s" % (to_text(method_name), to_text(callback_plugin), to_text(e))) 34 from traceback import format_tb 35 from sys import exc_info 36 display.vvv('Callback Exception: \n' + ' '.join(format_tb(exc_info()[2])))