ara/ara/utils.py

163 lines
5.1 KiB
Python

# Copyright (c) 2018 Red Hat, Inc.
#
# This file is part of ARA Records Ansible.
#
# ARA is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ARA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ARA. If not, see <http://www.gnu.org/licenses/>.
from ara import models
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from sqlalchemy import func
import hashlib
import pyfakefs.fake_filesystem as fake_filesystem
def generate_identifier(result):
"""
Returns a fixed length identifier based on a hash of a combined set of
playbook/task values which are as close as we can guess to unique for each
task.
"""
# Determine the playbook file path to use for the ID
if result.task.playbook and result.task.playbook.path:
playbook_file = result.task.playbook.path
else:
playbook_file = ''
play_path = u'%s.%s' % (playbook_file, result.task.play.name)
# Determine the task file path to use for the ID
if result.task.file and result.task.file.path:
task_file = result.task.file.path
else:
task_file = ''
task_path = u'%s.%s' % (task_file, result.task.name)
# Combine both of the above for a full path
identifier_path = u'%s.%s' % (play_path, task_path)
# Assign the identifier as a hash of the fully unique path.
identifier = hashlib.sha1(encodeutils.to_utf8(identifier_path)).hexdigest()
return identifier
def get_summary_stats(items, attr):
"""
Returns a dictionary of aggregated statistics for 'items' filtered by
"attr'. For example, it will aggregate statistics for a host across all
the playbook runs it has been a member of, with the following structure:
data[host.id] = {
'ok': 4
'changed': 4
...
}
"""
data = {}
for item in items:
stats = models.Stats.query.filter_by(**{attr: item.id})
data[item.id] = {
'ok': sum([int(stat.ok) for stat in stats]),
'changed': sum([int(stat.changed) for stat in stats]),
'failed': sum([int(stat.failed) for stat in stats]),
'skipped': sum([int(stat.skipped) for stat in stats]),
'unreachable': sum([int(stat.unreachable) for stat in stats])
}
# If we're aggregating stats for a playbook, also infer status
if attr is "playbook_id":
data[item.id]['status'] = _infer_status(item, data[item.id])
return data
def _infer_status(playbook, playbook_stats):
"""
Infer the status of a playbook run based on it's statistics or completion
"""
if not playbook.complete:
return 'incomplete'
if playbook_stats['failed'] >= 1 or playbook_stats['unreachable'] >= 1:
return 'failed'
else:
return 'success'
def fast_count(query):
"""
It turns out the built-in SQLAlchemy function for query.count() is pretty
slow. Alternatively, use this optimized function instead.
"""
count_query = (query
.statement.with_only_columns([func.count()]).order_by(None))
count = query.session.execute(count_query).scalar()
return count
def generate_tree(root, paths, mock_os):
"""
Given a file path, returns a JSON structure suitable for bootstrap-treeview
mock_os represents a faked filesystem generated from the playbook_treeview
method.
Credit: Mohammed Naser & David Moreau Simard
"""
tree = []
dentries = mock_os.listdir(root)
for d in dentries:
full_path = mock_os.path.join(root, d)
node = {
'text': d,
'href': '#%s' % d,
'state': {
'expanded': True
}
}
if mock_os.path.isdir(full_path):
node['nodes'] = generate_tree(full_path, paths, mock_os)
else:
node['icon'] = 'fa fa-file-code-o'
node['href'] = '#'
node['color'] = '#0088ce'
node['dataAttr'] = {
'toggle': 'modal',
'target': '#file_modal',
'load': paths[full_path] + '/'
}
tree.append(node)
return tree
def playbook_treeview(playbook):
"""
Creates a fake filesystem with playbook files and uses generate_tree() to
recurse and return a JSON structure suitable for bootstrap-treeview.
"""
fs = fake_filesystem.FakeFilesystem()
mock_os = fake_filesystem.FakeOsModule(fs)
files = models.File.query.filter(models.File.playbook_id.in_([playbook]))
paths = {}
for file in files:
fs.CreateFile(file.path)
paths[file.path] = file.id
return jsonutils.dumps(generate_tree('/', paths, mock_os),
sort_keys=True,
indent=2)