def take_action(self, args): test_cases = [] if args.playbook is not None: playbooks = args.playbook results = (models.TaskResult().query.join(models.Task).filter( models.TaskResult.task_id == models.Task.id).filter( models.Task.playbook_id.in_(playbooks))) else: results = models.TaskResult().query.all() for result in results: task_name = result.task.name if not task_name: task_name = result.task.action additional_results = { 'host': result.host.name, 'playbook_path': result.task.playbook.path } result_str = jsonutils.dumps(additional_results) test_path = \ u'{playbook_file}.{play_name}'.format( playbook_file=os.path.basename(result.task.playbook.path), play_name=result.task.play.name) test_case = TestCase(name=task_name, classname=test_path, elapsed_sec=result.duration.seconds, stdout=result_str) if result.status == 'skipped': test_case.add_skipped_info(message=result.result) elif ((result.status in ('failed', 'unreachable') and result.ignore_errors is False and 'EXPECTED FAILURE' not in task_name and 'TOGGLE RESULT' not in task_name) or (result.status == 'ok' and 'TOGGLE RESULT' in task_name)): test_case.add_failure_info(message=result.result) test_cases.append(test_case) test_suite = TestSuite('Ansible Tasks', test_cases) # TODO: junit_xml doesn't order the TestCase parameters. # This makes it so the order of the parameters for the same exact # TestCase is not guaranteed to be the same and thus results in a # different stdout (or file). This is easily reproducible on Py3. xml_string = six.text_type(test_suite.to_xml_string([test_suite])) if args.output_file == '-': if six.PY2: sys.stdout.write(encodeutils.safe_encode(xml_string)) else: sys.stdout.buffer.write(encodeutils.safe_encode(xml_string)) else: with open(args.output_file, 'wb') as f: f.write(encodeutils.safe_encode(xml_string))
def log_task(self, result, status, **kwargs): '''`log_task` is called when an individual task instance on a single host completes. It is responsible for logging a single `TaskResult` record to the database.''' LOG.debug('logging task result for task %s (%s), host %s', self.task.name, self.task.id, result._host.name) result.task_start = self.task.time_start result.task_end = datetime.now() host = self.get_or_create_host(result._host.name) if self.playbook not in host.playbooks: host.playbooks.append(self.playbook) self.taskresult = models.TaskResult( task=self.task, host=host, time_start=result.task_start, time_end=result.task_end, result=json.dumps(result._result), status=status, changed=result._result.get('changed', False), failed=result._result.get('failed', False), skipped=result._result.get('skipped', False), unreachable=result._result.get('unreachable', False), ignore_errors=kwargs.get('ignore_errors', False), ) db.session.add(self.taskresult) if self.task.action == 'setup' and 'ansible_facts' in result._result: values = json.dumps(result._result['ansible_facts']) facts = models.HostFacts(values=values) host.facts = facts db.session.add(facts)
def ansible_run(complete=True, gather_facts=True): '''Simulate a simple Ansible run by creating the expected database objects. This roughly approximates the following playbook: - hosts: host-<int> gather_facts: true tasks: - test-action: Where `<int>` is a random integer generated each time this function is called. Set the `complete` parameter to `False` to simulate an aborted Ansible run. Set the `gathered_facts` parameter to `False` to simulate a run with no facts gathered. ''' playbook = m.Playbook(path='testing.yml') play = m.Play(playbook=playbook, name='test play') task = m.Task(play=play, playbook=playbook, action='test-action') host = m.Host(name='host-%04d' % random.randint(0, 9999)) host.playbooks.append(playbook) result = m.TaskResult(task=task, status='ok', host=host, result='this is a test') ctx = dict(playbook=playbook, play=play, task=task, host=host, result=result) if gather_facts: facts = m.HostFacts(host=host, values='{"fact": "value"}') ctx['facts'] = facts for obj in ctx.values(): if hasattr(obj, 'start'): obj.start() db.session.add(obj) db.session.commit() if complete: stats = m.Stats(playbook=playbook, host=host) ctx['stats'] = stats db.session.add(stats) ctx['playbook'].complete = True for obj in ctx.values(): if hasattr(obj, 'stop'): obj.stop() db.session.commit() return ctx
def log_task(self, result, status, **kwargs): """ 'log_task' is called when an individual task instance on a single host completes. It is responsible for logging a single 'TaskResult' record to the database. """ LOG.debug('logging task result for task %s (%s), host %s', self.task.name, self.task.id, result._host.get_name()) # An include_role task might end up putting an IncludeRole object # inside the result object which we don't need # https://github.com/ansible/ansible/issues/30385 if 'include_role' in result._result: del result._result['include_role'] result.task_start = self.task.time_start result.task_end = datetime.now() host = self.get_or_create_host(result._host.get_name()) # Use Ansible's CallbackBase._dump_results in order to strip internal # keys, respect no_log directive, etc. if self.loop_items: # NOTE (dmsimard): There is a known issue in which Ansible can send # callback hooks out of order and "exit" the task before all items # have returned, this can cause one of the items to be missing # from the task result in ARA. # https://github.com/ansible/ansible/issues/24207 results = [self._dump_results(result._result)] for item in self.loop_items: results.append(self._dump_results(item._result)) results = jsonutils.loads(jsonutils.dumps(results)) else: results = jsonutils.loads(self._dump_results(result._result)) self.taskresult = models.TaskResult( task=self.task, host=host, time_start=result.task_start, time_end=result.task_end, result=jsonutils.dumps(results), status=status, changed=result._result.get('changed', False), failed=result._result.get('failed', False), skipped=result._result.get('skipped', False), unreachable=result._result.get('unreachable', False), ignore_errors=kwargs.get('ignore_errors', False), ) db.session.add(self.taskresult) db.session.commit() if self.task.action == 'setup' and 'ansible_facts' in result._result: values = jsonutils.dumps(result._result['ansible_facts']) facts = models.HostFacts(values=values) host.facts = facts db.session.add(facts) db.session.commit()
def model(self): return m.TaskResult(task=self.task, host=self.host, status=self.status, ignore_errors=self.ignore_errors, changed=self.changed, failed=self.failed, skipped=self.skipped, unreachable=self.unreachable, result=self.result)
def take_action(self, args): test_cases = [] if args.playbook is not None: playbooks = args.playbook results = (models.TaskResult().query .join(models.Task) .filter(models.TaskResult.task_id == models.Task.id) .filter(models.Task.playbook_id.in_(playbooks))) else: results = models.TaskResult().query.all() for result in results: task_name = result.task.name if not task_name: task_name = result.task.action additional_results = { 'host': result.host.name, 'playbook_path': result.task.playbook.path } result_str = json.dumps(additional_results) test_path = \ "{playbook_file}.{play_name}".format( playbook_file=os.path.basename(result.task.playbook.path), play_name=result.task.play.name) test_case = TestCase( name=task_name, classname=test_path, elapsed_sec=result.duration.seconds, stdout=result_str) if result.status == "skipped": test_case.add_skipped_info(message=result.result) elif (result.status in ("failed", "unreachable") and result.ignore_errors is False): test_case.add_failure_info(message=result.result) test_cases.append(test_case) test_suite = TestSuite("Ansible Tasks", test_cases) xml_string = test_suite.to_xml_string([test_suite]) if args.output_file == "-": sys.stdout.write(xml_string) else: with open(args.output_file, "w") as f: f.write(xml_string)
def setUp(self): super(TestModels, self).setUp() self.playbook = m.Playbook(path='testing.yml') self.play = m.Play( name='test play', playbook=self.playbook, ) self.task = m.Task( name='test task', play=self.play, playbook=self.playbook, ) self.data = m.Data(playbook=self.playbook, key='test key', value='test value') self.host = m.Host( name='localhost', playbook=self.playbook, ) self.host_facts = m.HostFacts(host=self.host, values=json.dumps('{"fact": "value"}')) self.task_result = m.TaskResult( task=self.task, status='ok', host=self.host, ) self.stats = m.Stats( playbook=self.playbook, host=self.host, changed=0, failed=0, skipped=0, unreachable=0, ok=0, ) for obj in [ self.playbook, self.play, self.task, self.data, self.host, self.task_result, self.stats ]: m.db.session.add(obj) m.db.session.commit()
def ansible_run(self, complete=True): '''Simulate a simple Ansible run by creating the expected database objects. This roughly approximates the following playbook: - hosts: localhost tasks: - test-action: Set the `complete` parameter to `False` to simulate an aborted Ansible run. ''' playbook = m.Playbook(path='testing.yml') play = m.Play(playbook=playbook, name='test play') task = m.Task(play=play, playbook=playbook, action='test-action') host = m.Host(name='localhost') host.playbooks.append(playbook) result = m.TaskResult(task=task, status='ok', host=host, result='this is a test') self.ctx = dict(playbook=playbook, play=play, task=task, host=host, result=result) for obj in self.ctx.values(): if hasattr(obj, 'start'): obj.start() db.session.add(obj) db.session.commit() if complete: stats = m.Stats(playbook=playbook, host=host) self.ctx['stats'] = stats db.session.add(stats) for obj in self.ctx.values(): if hasattr(obj, 'stop'): obj.stop() db.session.commit()
def setUp(self): m.db.create_all() self.playbook = m.Playbook(path='testing.yml') self.play = m.Play( name='test play', playbook=self.playbook, ) self.task = m.Task( name='test task', play=self.play, playbook=self.playbook, ) self.host = m.Host(name='localhost', ) self.task_result = m.TaskResult( task=self.task, status='ok', host=self.host, ) self.stats = m.Stats( playbook=self.playbook, host=self.host, changed=0, failed=0, skipped=0, unreachable=0, ok=0, ) for obj in [ self.playbook, self.play, self.task, self.host, self.task_result, self.stats ]: m.db.session.add(obj) m.db.session.commit()
def log_task(self, result, status, **kwargs): """ 'log_task' is called when an individual task instance on a single host completes. It is responsible for logging a single 'TaskResult' record to the database. """ LOG.debug('logging task result for task %s (%s), host %s', self.task.name, self.task.id, result._host.name) result.task_start = self.task.time_start result.task_end = datetime.now() host = self.get_or_create_host(result._host.name) # Use Ansible's CallbackBase._dump_results in order to strip internal # keys, respect no_log directive, etc. results = json.loads(self._dump_results(result._result)) self.taskresult = models.TaskResult( task=self.task, host=host, time_start=result.task_start, time_end=result.task_end, result=json.dumps(results), status=status, changed=result._result.get('changed', False), failed=result._result.get('failed', False), skipped=result._result.get('skipped', False), unreachable=result._result.get('unreachable', False), ignore_errors=kwargs.get('ignore_errors', False), ) db.session.add(self.taskresult) if self.task.action == 'setup' and 'ansible_facts' in result._result: values = json.dumps(result._result['ansible_facts']) facts = models.HostFacts(values=values) host.facts = facts db.session.add(facts)
def ansible_run(complete=True, gather_facts=True, ara_record=False): '''Simulate a simple Ansible run by creating the expected database objects. This roughly approximates the following playbook: - hosts: host-<int> gather_facts: true tasks: - test-action: when: not ara_record - ara_record: key: 'test key' value: 'test value' when: ara_record Where `<int>` is a random integer generated each time this function is called. Set the `complete` parameter to `False` to simulate an aborted Ansible run. Set the `gathered_facts` parameter to `False` to simulate a run with no facts gathered. Set the `ara_record` parameter to `True` to simulate a run with an ara_record task. ''' playbook = m.Playbook(path='testing.yml') play = m.Play(playbook=playbook, name='test play') host = m.Host(name='host-%04d' % random.randint(0, 9999), playbook=playbook) if ara_record: task = m.Task(play=play, playbook=playbook, action='ara_record') msg = 'Data recorded in ARA for this playbook.' else: task = m.Task(play=play, playbook=playbook, action='test-action') msg = 'This is a test' result = m.TaskResult(task=task, status='ok', host=host, result=msg) ctx = dict(playbook=playbook, play=play, task=task, host=host, result=result) if gather_facts: facts = m.HostFacts(host=host, values='{"fact": "value"}') ctx['facts'] = facts if ara_record: data = m.Data(playbook=playbook, key='test key', value='test value') ctx['data'] = data for obj in ctx.values(): if hasattr(obj, 'start'): obj.start() db.session.add(obj) db.session.commit() if complete: stats = m.Stats(playbook=playbook, host=host) ctx['stats'] = stats db.session.add(stats) ctx['playbook'].complete = True for obj in ctx.values(): if hasattr(obj, 'stop'): obj.stop() db.session.commit() return ctx
def take_action(self, args): # Setup where the output stream must go if args.output_file == '-': output_stream = sys.stdout else: output_stream = open(args.output_file, 'wb') # Create the output stream output = StreamResultToBytes(output_stream) # Create the test run output.startTestRun() if args.playbook is not None: playbooks = args.playbook results = (models.TaskResult().query.join(models.Task).filter( models.TaskResult.task_id == models.Task.id).filter( models.Task.playbook_id.in_(playbooks))) else: results = models.TaskResult().query.all() for result in results: # Generate a fixed length identifier for the task test_id = utils.generate_identifier(result) # Assign the test_status value if result.status in ('failed', 'unreachable'): if result.ignore_errors is False: test_status = 'xfail' else: test_status = 'fail' elif result.status == 'skipped': test_status = 'skip' else: test_status = 'success' # Determine the play file path if result.task.playbook and result.task.playbook.path: playbook_path = result.task.playbook.path else: playbook_path = '' # Determine the task file path if result.task.file and result.task.file.path: task_path = result.task.file.path else: task_path = '' # Assign the file_bytes value test_data = { 'host': result.host.name, 'playbook_id': result.task.playbook.id, 'playbook_path': playbook_path, 'play_name': result.task.play.name, 'task_action': result.task.action, 'task_action_lineno': result.task.lineno, 'task_id': result.task.id, 'task_name': result.task.name, 'task_path': task_path } file_bytes = encodeutils.safe_encode(jsonutils.dumps(test_data)) # Assign the start_time and stop_time value # The timestamp needs to be an epoch, so we need # to convert it. start_time = datetime.datetime.fromtimestamp( float(result.time_start.strftime('%s'))).replace( tzinfo=iso8601.UTC) end_time = datetime.datetime.fromtimestamp( float(result.time_end.strftime('%s'))).replace( tzinfo=iso8601.UTC) # Output the start of the event output.status(test_id=test_id, timestamp=start_time) # Output the end of the event output.status(test_id=test_id, test_status=test_status, test_tags=None, runnable=False, file_name=test_id, file_bytes=file_bytes, timestamp=end_time, eof=True, mime_type='text/plain; charset=UTF8') output.stopTestRun()
def log_task(self, result, status, **kwargs): """ 'log_task' is called when an individual task instance on a single host completes. It is responsible for logging a single 'TaskResult' record to the database. """ log.debug('Logging task result for task %s (%s), host %s', self.task.name, self.task.id, result._host.get_name()) # An include_role task might end up putting an IncludeRole object # inside the result object which we don't need # https://github.com/ansible/ansible/issues/30385 if 'include_role' in result._result: del result._result['include_role'] result.task_start = self.task.time_start result.task_end = datetime.now() host = self.get_or_create_host(result._host.get_name()) # Use Ansible's CallbackBase._dump_results in order to strip internal # keys, respect no_log directive, etc. if self.loop_items: # NOTE (dmsimard): There is a known issue in which Ansible can send # callback hooks out of order and "exit" the task before all items # have returned, this can cause one of the items to be missing # from the task result in ARA. # https://github.com/ansible/ansible/issues/24207 results = [self._dump_results(result._result)] for item in self.loop_items: results.append(self._dump_results(item._result)) results = jsonutils.loads(jsonutils.dumps(results)) else: results = jsonutils.loads(self._dump_results(result._result)) # Ignore errors can be "yes" instead of a proper boolean in <2.3 # for some reason ignore_errors = kwargs.get('ignore_errors', False) if LooseVersion(ansible_version) < LooseVersion('2.3.0'): if not isinstance(ignore_errors, bool): ignore_errors = True if ignore_errors == "yes" else False if self.task.action == 'setup' and 'ansible_facts' in results: # Potentially sanitize some Ansible facts to prevent them from # being saved both in the host facts and in the task results. for fact in app.config['ARA_IGNORE_FACTS']: if fact in results['ansible_facts']: msg = "Not saved by ARA as configured by ARA_IGNORE_FACTS" results['ansible_facts'][fact] = msg values = jsonutils.dumps(results['ansible_facts']) facts = models.HostFacts(values=values) host.facts = facts db.session.add(facts) db.session.commit() self.taskresult = models.TaskResult( task=self.task, host=host, time_start=result.task_start, time_end=result.task_end, result=jsonutils.dumps(results), status=status, changed=result._result.get('changed', False), failed=result._result.get('failed', False), skipped=result._result.get('skipped', False), unreachable=result._result.get('unreachable', False), ignore_errors=ignore_errors, ) db.session.add(self.taskresult) db.session.commit()