| savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 1 | import jinja2 | 
|  | 2 | import six | 
|  | 3 | import abc | 
|  | 4 | import os | 
|  | 5 |  | 
|  | 6 | from check_versions.common import const | 
|  | 7 |  | 
|  | 8 | pkg_dir = os.path.dirname(__file__) | 
|  | 9 | pkg_dir = os.path.join(pkg_dir, os.pardir) | 
|  | 10 | pkg_dir = os.path.normpath(pkg_dir) | 
|  | 11 |  | 
|  | 12 |  | 
|  | 13 | def shortname(node_fqdn): | 
|  | 14 | # form shortname out of node fqdn | 
|  | 15 | return node_fqdn.split(".", 1)[0] | 
|  | 16 |  | 
|  | 17 |  | 
|  | 18 | def is_equal(pkg_dict): | 
|  | 19 | # compare versions of given package | 
|  | 20 | return pkg_dict['installed'] == pkg_dict['candidate'] | 
|  | 21 |  | 
|  | 22 |  | 
|  | 23 | def is_active(node_dict): | 
|  | 24 | # check node status in node dict | 
|  | 25 | return node_dict['status'] == const.NODE_UP | 
|  | 26 |  | 
|  | 27 |  | 
|  | 28 | def line_breaks(text): | 
|  | 29 | # replace python linebreaks with html breaks | 
|  | 30 | return text.replace("\n", "<br />") | 
|  | 31 |  | 
|  | 32 |  | 
|  | 33 | @six.add_metaclass(abc.ABCMeta) | 
|  | 34 | class _Base(object): | 
|  | 35 | def __init__(self): | 
|  | 36 | self.jinja2_env = self.init_jinja2_env() | 
|  | 37 |  | 
|  | 38 | @abc.abstractmethod | 
|  | 39 | def __call__(self, payload): | 
|  | 40 | pass | 
|  | 41 |  | 
|  | 42 | @staticmethod | 
|  | 43 | def init_jinja2_env(): | 
|  | 44 | return jinja2.Environment( | 
|  | 45 | loader=jinja2.FileSystemLoader(os.path.join(pkg_dir, 'templates')), | 
|  | 46 | trim_blocks=True, | 
|  | 47 | lstrip_blocks=True) | 
|  | 48 |  | 
|  | 49 |  | 
|  | 50 | class _TMPLBase(_Base): | 
|  | 51 | @abc.abstractproperty | 
|  | 52 | def tmpl(self): | 
|  | 53 | pass | 
|  | 54 |  | 
|  | 55 | @staticmethod | 
|  | 56 | def _count_totals(data): | 
|  | 57 | data['counters']['total_nodes'] = len(data['nodes']) | 
|  | 58 |  | 
|  | 59 | def __call__(self, nodes): | 
|  | 60 | # init data structures | 
|  | 61 | data = self.common_data() | 
|  | 62 | data.update({ | 
|  | 63 | "nodes": nodes | 
|  | 64 | }) | 
|  | 65 |  | 
|  | 66 | # add template specific data | 
|  | 67 | self._extend_data(data) | 
|  | 68 |  | 
|  | 69 | # do counts global | 
|  | 70 | self._count_totals(data) | 
|  | 71 |  | 
|  | 72 | # specific filters | 
|  | 73 | self.jinja2_env.filters['shortname'] = shortname | 
|  | 74 | self.jinja2_env.filters['is_equal'] = is_equal | 
|  | 75 | self.jinja2_env.filters['is_active'] = is_active | 
|  | 76 | self.jinja2_env.filters['linebreaks'] = line_breaks | 
|  | 77 |  | 
|  | 78 | # render! | 
|  | 79 | tmpl = self.jinja2_env.get_template(self.tmpl) | 
|  | 80 | return tmpl.render(data) | 
|  | 81 |  | 
|  | 82 | def common_data(self): | 
|  | 83 | return { | 
|  | 84 | 'counters': {}, | 
|  | 85 | 'salt_info': {} | 
|  | 86 | } | 
|  | 87 |  | 
|  | 88 | def _extend_data(self, data): | 
|  | 89 | pass | 
|  | 90 |  | 
|  | 91 |  | 
|  | 92 | # Package versions report | 
| savex | ce010ba | 2018-04-27 09:49:23 +0200 | [diff] [blame^] | 93 | class HTMLPackageCandidates(_TMPLBase): | 
| savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 94 | tmpl = "pkg_versions_tmpl.j2" | 
|  | 95 |  | 
|  | 96 | @staticmethod | 
|  | 97 | def is_fail_uniq(p_dict, p_name, nodes, node_name): | 
|  | 98 | # look up package fail for nodes with similar role | 
|  | 99 | _tgroup = nodes[node_name]['node_group'] | 
|  | 100 | # filter all nodes with the same role | 
|  | 101 | _nodes_list = filter( | 
|  | 102 | lambda nd: nodes[nd]['node_group'] == _tgroup and nd != node_name, | 
|  | 103 | nodes | 
|  | 104 | ) | 
|  | 105 | # lookup same package | 
|  | 106 | _fail_uniq = False | 
|  | 107 | for _node_name in _nodes_list: | 
|  | 108 | # check if there is a package present on node | 
|  | 109 | _nd = nodes[_node_name]['packages'] | 
|  | 110 | if p_name not in _nd: | 
|  | 111 | continue | 
|  | 112 | # if both backages has same version and differ from candidate | 
|  | 113 | if p_dict['candidate'] == _nd[p_name]['candidate'] \ | 
|  | 114 | and _nd[p_name]['candidate'] == _nd[p_name]['installed']: | 
|  | 115 | # it is not uniq, mark and break | 
|  | 116 | _fail_uniq = True | 
|  | 117 | return _fail_uniq | 
|  | 118 |  | 
|  | 119 | def _extend_data(self, data): | 
|  | 120 | _all_pkg = 0 | 
|  | 121 | for key, value in data['nodes'].iteritems(): | 
|  | 122 | # add count of packages for this node to total | 
|  | 123 | _all_pkg += len(value.keys()) | 
|  | 124 |  | 
|  | 125 | # count differences | 
|  | 126 | data['counters'][key] = {} | 
|  | 127 | data['counters'][key]['packages'] = len(value['packages'].keys()) | 
|  | 128 | data['counters'][key]['package_diff'] = 0 | 
|  | 129 | for pkg_name, pkg_value in value['packages'].iteritems(): | 
|  | 130 | if pkg_value['installed'] != pkg_value['candidate']: | 
|  | 131 | pkg_value['is_equal'] = False | 
|  | 132 | pkg_value['fail_uniq'] = self.is_fail_uniq( | 
|  | 133 | pkg_value, | 
|  | 134 | pkg_name, | 
|  | 135 | data['nodes'], | 
|  | 136 | key | 
|  | 137 | ) | 
|  | 138 | data['counters'][key]['package_diff'] += 1 | 
|  | 139 | else: | 
|  | 140 | pkg_value['is_equal'] = True | 
|  | 141 | pkg_value['fail_uniq'] = False | 
|  | 142 |  | 
|  | 143 | data['counters']['total_packages'] = _all_pkg | 
|  | 144 |  | 
|  | 145 |  | 
|  | 146 | class ReportToFile(object): | 
|  | 147 | def __init__(self, report, target): | 
|  | 148 | self.report = report | 
|  | 149 | self.target = target | 
|  | 150 |  | 
|  | 151 | def __call__(self, payload): | 
|  | 152 | payload = self.report(payload) | 
|  | 153 |  | 
|  | 154 | if isinstance(self.target, six.string_types): | 
|  | 155 | self._wrapped_dump(payload) | 
|  | 156 | else: | 
|  | 157 | self._dump(payload, self.target) | 
|  | 158 |  | 
|  | 159 | def _wrapped_dump(self, payload): | 
|  | 160 | with open(self.target, 'wt') as target: | 
|  | 161 | self._dump(payload, target) | 
|  | 162 |  | 
|  | 163 | @staticmethod | 
|  | 164 | def _dump(payload, target): | 
|  | 165 | target.write(payload) |