Network check HTML report
- uniform map generation
- POC for additional checks on env
Change-Id: I52edcc94f4d9826cbfb1159e5311514097a15f62
Related-PROD: PROD-32792
diff --git a/cfg_checker/common/const.py b/cfg_checker/common/const.py
index 966f3d3..5826f43 100644
--- a/cfg_checker/common/const.py
+++ b/cfg_checker/common/const.py
@@ -35,7 +35,7 @@
ACT_NA: ""
}
-all_statuses = {
+all_pkg_statuses = {
VERSION_OK: "ok",
VERSION_UP: "upgraded",
VERSION_DOWN: "downgraded",
@@ -43,6 +43,11 @@
VERSION_NA: "no status"
}
+node_status = {
+ NODE_UP: "up",
+ NODE_DOWN: "down"
+}
+
uknown_code = "unk"
all_roles_map = {
diff --git a/cfg_checker/helpers/errors.py b/cfg_checker/helpers/errors.py
index 83ae803..95e1495 100644
--- a/cfg_checker/helpers/errors.py
+++ b/cfg_checker/helpers/errors.py
@@ -102,12 +102,12 @@
# format message
_msg = "### {}:\n Description: {}\n{}".format(
_code,
- self._get_error_type_text(self._errors[index]['type']),
+ self.get_error_type_text(self._errors[index]['type']),
"\n".join(_data)
)
return _msg
- def _get_error_type_text(self, err_type):
+ def get_error_type_text(self, err_type):
if err_type not in self._types:
raise ErrorMappingException(
"type code {} not found".format(err_type)
diff --git a/cfg_checker/modules/network/__init__.py b/cfg_checker/modules/network/__init__.py
index 6b06022..6b9013a 100644
--- a/cfg_checker/modules/network/__init__.py
+++ b/cfg_checker/modules/network/__init__.py
@@ -66,15 +66,6 @@
return _parser
-def _prepare_map():
- _mapper = mapper.NetworkMapper()
- _mapper.map_network(_mapper.RECLASS)
- _mapper.map_network(_mapper.RUNTIME)
- _mapper.map_network(_mapper.CONFIG)
-
- return _mapper
-
-
def do_check(args):
# Net Checks
# should not print map, etc...
@@ -104,6 +95,10 @@
_filename = args_utils.get_arg(args, 'html')
netChecker = checker.NetworkChecker()
+ netChecker.check_networks(map=False)
+
+ # save what was collected
+ netChecker.errors.save_iteration_data()
netChecker.create_html_report(_filename)
return
@@ -114,7 +109,9 @@
# Should generate network map to console or HTML
logger_cli.info("# Network report")
- networkMap = _prepare_map()
+ networkMap = mapper.NetworkMapper()
+ networkMap.prepare_all_maps()
+ networkMap.create_map()
networkMap.print_map()
return
diff --git a/cfg_checker/modules/network/checker.py b/cfg_checker/modules/network/checker.py
index 89db6ba..acd3bb1 100644
--- a/cfg_checker/modules/network/checker.py
+++ b/cfg_checker/modules/network/checker.py
@@ -10,11 +10,13 @@
self.errors = NetworkErrors()
self.mapper = NetworkMapper(self.errors)
- def check_networks(self):
+ def check_networks(self, map=True):
self.mapper.map_network(self.mapper.RECLASS)
self.mapper.map_network(self.mapper.RUNTIME)
- self.mapper.print_map()
+ self.mapper.create_map()
+ if map:
+ self.mapper.print_map()
def print_summary(self):
logger_cli.info(self.errors.get_summary(print_zeros=False))
@@ -39,10 +41,10 @@
filename
)
_report({
- "nodes": self.nodes,
- "network": {},
- "mcp_release": self.mcp_release,
- "openstack_release": self.openstack_release
+ "nodes": self.mapper.nodes,
+ "map": self.mapper.map,
+ "mcp_release": self.mapper.cluster['mcp_release'],
+ "openstack_release": self.mapper.cluster['openstack_release']
})
logger_cli.info("-> Done")
diff --git a/cfg_checker/modules/network/mapper.py b/cfg_checker/modules/network/mapper.py
index c3e3b73..ba9a256 100644
--- a/cfg_checker/modules/network/mapper.py
+++ b/cfg_checker/modules/network/mapper.py
@@ -36,6 +36,7 @@
# init networks and nodes
self.networks = {}
self.nodes = salt_master.get_nodes()
+ self.cluster = salt_master.get_info()
# init and pre-populate interfaces
self.interfaces = {k: {} for k in self.nodes}
# Init errors class
@@ -45,6 +46,11 @@
logger_cli.debug("... init error logs folder")
self.errors = NetworkErrors()
+ def prepare_all_maps(self):
+ self.map_network(self.RECLASS)
+ self.map_network(self.RUNTIME)
+ self.map_network(self.CONFIG)
+
# adding net data to tree
def _add_data(self, _list, _n, _h, _d):
if _n not in _list:
@@ -253,32 +259,23 @@
self.networks[source] = _networks
return _networks
- def print_map(self):
- """
- Create text report for CLI
+ def create_map(self):
+ """Create all needed elements for map output
:return: none
"""
_runtime = self.networks[self.RUNTIME]
_reclass = self.networks[self.RECLASS]
- logger_cli.info("# Networks")
- logger_cli.info(
- " {0:8} {1:25} {2:25} {3:6} {4:10} {5:10} {6}/{7}".format(
- "Host",
- "IF",
- "IP",
- "Proto",
- "MTU",
- "State",
- "Gate",
- "Def.Gate"
- )
- )
+
+ # main networks, target vars
+ _map = {}
# No matter of proto, at least one IP will be present for the network
+ # we interested in, since we are to make sure that L3 level
+ # is configured according to reclass model
for network in _reclass:
# shortcuts
_net = str(network)
- logger_cli.info("-> {}".format(_net))
+ _map[_net] = {}
if network not in _runtime:
# reclass has network that not found in runtime
self.errors.add_error(
@@ -290,6 +287,7 @@
# hostnames
names = sorted(_runtime[network].keys())
for hostname in names:
+ _notes = []
node = hostname.split('.')[0]
if not salt_master.is_node_available(hostname, log=False):
logger_cli.info(
@@ -300,9 +298,15 @@
self.errors.NET_NODE_NON_RESPONSIVE,
host=hostname
)
+ _notes.append(
+ self.errors.get_error_type_text(
+ self.errors.NET_NODE_NON_RESPONSIVE
+ )
+ )
continue
# lookup interface name on node using network CIDR
_if_name = _runtime[network][hostname][0]["name"]
+ _raw = self.interfaces[hostname][_if_name]['runtime']
# get proper reclass
_r = self.interfaces[hostname][_if_name]['reclass']
_if_name_suffix = ""
@@ -315,6 +319,11 @@
host=hostname,
if_name=_if_name
)
+ _notes.append(
+ self.errors.get_error_type_text(
+ self.errors.NET_NODE_UNEXPECTED_IF
+ )
+ )
_if_rc = "*"
if "proto" in _r:
@@ -379,6 +388,11 @@
ip=_ip_str,
gateway=_gate
)
+ _notes.append(
+ self.errors.get_error_type_text(
+ self.errors.NET_UNEXPECTED_GATEWAY
+ )
+ )
_gate_error = "*"
# IF status in reclass
@@ -389,6 +403,11 @@
host=hostname,
if_name=_if_name
)
+ _notes.append(
+ self.errors.get_error_type_text(
+ self.errors.NET_NO_RC_IF_STATUS
+ )
+ )
_up_error = "*"
_rc_mtu = _r['mtu'] if 'mtu' in _r else None
@@ -408,6 +427,11 @@
reclass_mtu=_rc_mtu,
runtime_mtu=_host['mtu']
)
+ _notes.append(
+ self.errors.get_error_type_text(
+ self.errors.NET_MTU_MISMATCH
+ )
+ )
_rc_mtu_s = "/" + _rc_mtu_s
_mtu_error = "*"
else:
@@ -424,48 +448,111 @@
if_cidr=_ip_str,
if_mtu=_host['mtu']
)
+ _notes.append(
+ self.errors.get_error_type_text(
+ self.errors.NET_MTU_EMPTY
+ )
+ )
_mtu_error = "*"
else:
# this is a VIP
_if_name = " "*7
_if_name_suffix = ""
_ip_str += " VIP"
- # Host IF IP Proto MTU State Gate Def.Gate
- _text = "{:7} {:17} {:25} {:6} {:10} " \
- "{:10} {} / {}".format(
- _if_name + _if_rc,
- _if_name_suffix,
- _ip_str,
- _proto,
- _host['mtu'] + _rc_mtu_s + _mtu_error,
- _host['state'] + _up_error,
- _gate + _gate_error,
- _d_gate_str
- )
- logger_cli.info(
- " {0:8} {1}".format(
- node,
- _text
+ # Save all data
+ _values = {
+ "interface": _if_name,
+ "interface_error": _if_rc,
+ "interface_note": _if_name_suffix,
+ "ip_address": _ip_str,
+ "address_type": _proto,
+ "rt_mtu": _host['mtu'],
+ "rc_mtu": _rc_mtu_s,
+ "mtu_error": _mtu_error,
+ "status": _host['state'],
+ "status_error": _up_error,
+ "subnet_gateway": _gate,
+ "subnet_gateway_error": _gate_error,
+ "default_gateway": _d_gate_str,
+ "raw_data": _raw,
+ "error_note": " and ".join(_notes)
+ }
+ if node in _map[_net]:
+ # add if to host
+ _map[_net][node].append(_values)
+ else:
+ _map[_net][node] = [_values]
+ _notes = []
+
+ # save map
+ self.map = _map
+ # other runtime networks found
+ # docker, etc
+
+ return
+
+ def print_map(self):
+ """
+ Create text report for CLI
+
+ :return: none
+ """
+ logger_cli.info("# Networks")
+ logger_cli.info(
+ " {0:8} {1:25} {2:25} {3:6} {4:10} {5:10} {6}/{7}".format(
+ "Host",
+ "IF",
+ "IP",
+ "Proto",
+ "MTU",
+ "State",
+ "Gate",
+ "Def.Gate"
+ )
+ )
+ for network in self.map.keys():
+ logger_cli.info("-> {}".format(network))
+ for hostname in self.map[network].keys():
+ node = hostname.split('.')[0]
+ _n = self.map[network][hostname]
+ for _i in _n:
+ # Host IF IP Proto MTU State Gate Def.Gate
+ _text = "{:7} {:17} {:25} {:6} {:10} " \
+ "{:10} {} / {}".format(
+ _i['interface'] + _i['interface_error'],
+ _i['interface_note'],
+ _i['ip_address'],
+ _i['address_type'],
+ _i['rt_mtu'] + _i['rc_mtu'] + _i['mtu_error'],
+ _i['status'] + _i['status_error'],
+ _i['subnet_gateway'] +
+ _i['subnet_gateway_error'],
+ _i['default_gateway']
)
- )
-
- logger_cli.info("\n# Other networks")
- _other = [n for n in _runtime if n not in _reclass]
- for network in _other:
- logger_cli.info("-> {}".format(str(network)))
- names = sorted(_runtime[network].keys())
-
- for hostname in names:
- for _n in _runtime[network][hostname]:
- _ifs = [str(ifs.ip) for ifs in _n['ifs']]
- _text = "{:25} {:25} {:6} {:10} {}".format(
- _n['name'],
- ", ".join(_ifs),
- "-",
- _n['mtu'],
- _n['state']
- )
logger_cli.info(
- " {0:8} {1}".format(hostname.split('.')[0], _text)
+ " {0:8} {1}".format(
+ node,
+ _text
+ )
)
- logger_cli.info("\n")
+
+ # logger_cli.info("\n# Other networks")
+ # _other = [n for n in _runtime if n not in _reclass]
+ # for network in _other:
+ # logger_cli.info("-> {}".format(str(network)))
+ # names = sorted(_runtime[network].keys())
+
+ # for hostname in names:
+ # for _n in _runtime[network][hostname]:
+ # _ifs = [str(ifs.ip) for ifs in _n['ifs']]
+ # _text = "{:25} {:25} {:6} {:10} {}".format(
+ # _n['name'],
+ # ", ".join(_ifs),
+ # "-",
+ # _n['mtu'],
+ # _n['state']
+ # )
+ # logger_cli.info(
+ # " {0:8} {1}".format(hostname.split('.')[0], _text)
+ # )
+ # logger_cli.info("\n")
diff --git a/cfg_checker/nodes.py b/cfg_checker/nodes.py
index 5e535b4..ca4e261 100644
--- a/cfg_checker/nodes.py
+++ b/cfg_checker/nodes.py
@@ -140,6 +140,53 @@
self.gather_node_info()
return self.nodes
+ def get_info(self):
+ _info = {
+ 'mcp_release': self.mcp_release,
+ 'openstack_release': self.openstack_release
+ }
+ return _info
+
+ def get_cmd_for_nodes(self, cmd, target_key, target_dict=None):
+ """Function runs. cmd.run and parses result into place
+ or into dict structure provided
+
+ :return: no return value, data pulished internally
+ """
+ logger_cli.debug(
+ "... collecting results for '{}'".format(cmd)
+ )
+ if target_dict:
+ _nodes = target_dict
+ else:
+ _nodes = self.nodes
+ _result = self.execute_cmd_on_active_nodes(cmd)
+ for node, data in _nodes.iteritems():
+ if node in self.skip_list:
+ logger_cli.debug(
+ "... '{}' skipped while collecting '{}'".format(
+ node,
+ cmd
+ )
+ )
+ continue
+ # Prepare target key
+ if target_key not in data:
+ data[target_key] = None
+ # Save data
+ if data['status'] == const.NODE_DOWN:
+ data[target_key] = None
+ elif not _result[node]:
+ logger_cli.debug(
+ "... '{}' not responded after '{}'".format(
+ node,
+ config.salt_timeout
+ )
+ )
+ data[target_key] = None
+ else:
+ data[target_key] = _result[node]
+
def get_specific_pillar_for_nodes(self, pillar_path):
"""Function gets pillars on given path for all nodes
@@ -322,6 +369,20 @@
self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
return _r
+ def execute_cmd_on_active_nodes(self, cmd):
+ # execute cmd
+ self.not_responded = []
+ _r = self.salt.cmd(
+ self.active_nodes_compound,
+ 'cmd.run',
+ param=cmd,
+ expr_form="compound"
+ )
+
+ # all false returns means that there is no response
+ self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
+ return _r
+
def is_node_available(self, node, log=True):
if node in self.skip_list:
if log:
diff --git a/cfg_checker/reports/reporter.py b/cfg_checker/reports/reporter.py
index a624dd3..8059fab 100644
--- a/cfg_checker/reports/reporter.py
+++ b/cfg_checker/reports/reporter.py
@@ -4,6 +4,7 @@
from cfg_checker.common import const
from cfg_checker.common import logger_cli
+from cfg_checker.nodes import salt_master
import jinja2
@@ -13,6 +14,12 @@
pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir)
pkg_dir = os.path.normpath(pkg_dir)
+# % threshhold values
+_disk_warn = 80
+_disk_critical = 90
+_ram_warn = 5
+_ram_critical = 3
+
def line_breaks(text):
# replace python linebreaks with html breaks
@@ -41,7 +48,7 @@
return sorted(_list)[-1]
-def make_action_label(act):
+def make_pkg_action_label(act):
_act_labels = {
const.ACT_UPGRADE: "Upgrade possible",
const.ACT_NEED_UP: "Needs upgrade",
@@ -52,7 +59,7 @@
return _act_labels[act]
-def make_action_class(act):
+def make_pkg_action_class(act):
_act_classes = {
const.ACT_UPGRADE: "possible",
const.ACT_NEED_UP: "needs_up",
@@ -63,7 +70,7 @@
return _act_classes[act]
-def make_status_label(sts):
+def make_pkg_status_label(sts):
_status_labels = {
const.VERSION_OK: "OK",
const.VERSION_UP: "Upgraded",
@@ -74,8 +81,12 @@
return _status_labels[sts]
-def make_status_class(sts):
- return const.all_statuses[sts]
+def make_pkg_status_class(sts):
+ return const.all_pkg_statuses[sts]
+
+
+def make_node_status(sts):
+ return const.node_status[sts]
def make_repo_info(repos):
@@ -144,10 +155,11 @@
self.jinja2_env.filters['get_max'] = get_max
self.jinja2_env.filters['get_sorted_keys'] = get_sorted_keys
- self.jinja2_env.filters['make_status_label'] = make_status_label
- self.jinja2_env.filters['make_status_class'] = make_status_class
- self.jinja2_env.filters['make_action_label'] = make_action_label
- self.jinja2_env.filters['make_action_class'] = make_action_class
+ self.jinja2_env.filters['pkg_status_label'] = make_pkg_status_label
+ self.jinja2_env.filters['pkg_status_class'] = make_pkg_status_class
+ self.jinja2_env.filters['pkg_action_label'] = make_pkg_action_label
+ self.jinja2_env.filters['pkg_action_class'] = make_pkg_action_class
+ self.jinja2_env.filters['node_status_class'] = make_node_status
self.jinja2_env.filters['make_repo_info'] = make_repo_info
# render!
@@ -194,6 +206,130 @@
class HTMLNetworkReport(_TMPLBase):
tmpl = "network_check_tmpl.j2"
+ def _extend_data(self, data):
+ def get_bytes(value):
+ if value[-1] == 'G':
+ return int(float(value[:-1]) * 1024 * 1024 * 1024)
+ elif value[-1] == 'M':
+ return int(float(value[:-1]) * 1024 * 1024)
+ elif value[-1] == 'K':
+ return int(float(value[:-1]) * 1024)
+ else:
+ return int(value)
+
+ def _lscpu(field, key, _dict):
+ _f_cmd = salt_master.get_cmd_for_nodes
+ _cmd = "lscpu | grep -e \"^{}:\" | cut -d\":\" -f2 " \
+ "| sed -e 's/^[[:space:]]*//'"
+ _f_cmd(_cmd.format(field), key, target_dict=_dict)
+
+ def _free(field, key, _dict):
+ _f_cmd = salt_master.get_cmd_for_nodes
+ _cmd = "free -h | sed -n '/Mem/s/ \\+/ /gp' | cut -d\" \" -f {}"
+ _f_cmd(_cmd.format(field), key, target_dict=_dict)
+
+ def _services(_dict):
+ _key = "services"
+ _key_r = "services_raw"
+ _f_cmd = salt_master.get_cmd_for_nodes
+ _cmd = "service --status-all"
+ _f_cmd(_cmd, _key_r, target_dict=_dict)
+ for node, dt in _dict.iteritems():
+ dt[_key] = {}
+ lines = dt[_key_r].splitlines()
+ for line in lines:
+ li = line.split()
+ _status = li[1]
+ _name = li[3]
+ if _status == '-':
+ dt[_key][_name] = False
+ elif _status == '+':
+ dt[_key][_name] = True
+ else:
+ dt[_key][_name] = None
+ dt.pop(_key_r)
+
+ data["const"] = {
+ "ram_warn": _ram_warn,
+ "ram_critical": _ram_critical,
+ "disk_warn": _disk_warn,
+ "disk_critical": _disk_critical
+ }
+
+ # get kernel version
+ salt_master.get_cmd_for_nodes(
+ "uname -r",
+ "kernel",
+ target_dict=data["nodes"]
+ )
+ # cpu info
+ # Sample: VT-x, KVM, full
+ _lscpu("Virtualization", "virt_mode", data["nodes"])
+ _lscpu("Hypervisor vendor", "virt_vendor", data["nodes"])
+ _lscpu("Virtualization type", "virt_type", data["nodes"])
+ # sample: 4
+ _lscpu("CPU(s)", "cpus", data["nodes"])
+
+ # free ram
+ # sample: 16425392 14883144 220196
+ _free("2", "ram_total", data["nodes"])
+ _free("3", "ram_used", data["nodes"])
+ _free("4", "ram_free", data["nodes"])
+ _free("7", "ram_available", data["nodes"])
+ for _data in data["nodes"].itervalues():
+ _total = get_bytes(_data["ram_total"])
+ _avail = get_bytes(_data["ram_available"])
+ _m = _avail * 100.0 / _total
+ if _m < _ram_critical:
+ _data["ram_status"] = "fail"
+ elif _m < _ram_warn:
+ _data["ram_status"] = "warn"
+ else:
+ _data["ram_status"] = ""
+
+ # disk space
+ # sample: /dev/vda1 78G 33G 45G 43%
+ salt_master.get_cmd_for_nodes(
+ "df -h | sed -n '/^\\/dev/s/ \\+/ /gp' | cut -d\" \" -f 1-5",
+ "disk_raw",
+ target_dict=data["nodes"]
+ )
+ for _data in data["nodes"].itervalues():
+ _data["disk"] = {}
+ # show first device row by default
+ _data["disk_max_dev"] = None
+ _d = _data["disk"]
+ _r = _data["disk_raw"]
+ _r = _r.splitlines()
+ _max = -1
+ for idx in range(0, len(_r)):
+ _t = _r[idx].split()
+ _d[_t[0]] = {}
+ _d[_t[0]]['v'] = _t[1:]
+ _chk = int(_t[-1].split('%')[0])
+ if _chk > _max:
+ _data["disk_max_dev"] = _t[0]
+ _max = _chk
+ if _chk > _disk_critical:
+ _d[_t[0]]['f'] = "fail"
+ elif _chk > _disk_warn:
+ _d[_t[0]]['f'] = "warn"
+ else:
+ _d[_t[0]]['f'] = ""
+
+ # prepare networks data for report
+ for net, net_v in data['map'].iteritems():
+ for node, ifs in net_v.iteritems():
+ for d in ifs:
+ _err = "fail"
+ d['interface_error'] = _err if d['interface_error'] else ""
+ d['mtu_error'] = _err if d['mtu_error'] else ""
+ d['status_error'] = _err if d['status_error'] else ""
+ d['subnet_gateway_error'] = \
+ _err if d['subnet_gateway_error'] else ""
+
+ _services(data["nodes"])
+
class ReportToFile(object):
def __init__(self, report, target):
diff --git a/scripts/sniffer.py b/scripts/sniffer.py
index 612ff20..9b6830c 100644
--- a/scripts/sniffer.py
+++ b/scripts/sniffer.py
@@ -112,23 +112,27 @@
s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800))
u = unpack()
-count = 5
+count = 32
while count > 0:
count -= 1
# Capture packets from network
pkt = s.recvfrom(65565)
- print "\n\n===>> [+] ------------ Ethernet Header----- [+]"
+ print "\n\n=== [+] ------------ Ethernet Header----- [+]"
# print data on terminal
for i in u.eth_header(pkt[0][0:14]).iteritems():
a, b = i
print "{} : {} | ".format(a, b),
- print "\n===>> [+] ------------ IP Header ------------[+]"
+ print "\n=== [+] ------------ IP Header ------------[+]"
for i in u.ip_header(pkt[0][14:34]).iteritems():
a, b = i
print "{} : {} | ".format(a, b),
- print "\n===>> [+] ------------ Tcp Header ----------- [+]"
+ print "\n== [+] ------------ Tcp Header ----------- [+]"
for i in u.tcp_header(pkt[0][34:54]).iteritems():
a, b = i
print "{} : {} | ".format(a, b),
+ print "\n===== Data ===="
+ print pkt[0][54:]
+ print "\n======="
+ print pkt[1:]
diff --git a/setup.py b/setup.py
index 0659294..9c9fbf1 100644
--- a/setup.py
+++ b/setup.py
@@ -33,8 +33,8 @@
setup(
- name="Mirantis Cloud Configuration Checker",
- version="0.4",
+ name="mcp-checker",
+ version="0.41a",
author="Alex Savatieiev",
author_email="osavatieiev@mirantis.com",
classifiers=[
diff --git a/templates/network_check_tmpl.j2 b/templates/network_check_tmpl.j2
index 9f0f556..c26a115 100644
--- a/templates/network_check_tmpl.j2
+++ b/templates/network_check_tmpl.j2
@@ -14,30 +14,176 @@
/* Node rows*/
.node {
+ font-family: "LaoSangamMN", Monaco, monospace;
+ font-size: 0.8em;
display: inline-block;
background-color: white;
}
+ .collapsable {
+ font-family: "LaoSangamMN", Monaco, monospace;
+ font-size: 0.8em;
+ display: none;
+ background-color: white;
+ visibility: hidden;
+ }
+ .collapsable.in {
+ visibility: visible;
+ display: inline-block;
+ }
+
+ div.services > .collapsable.in {
+ display: table-row;
+ }
- tr.node > td {
+ tr.node > td, tr.collapsable > td {
display: block;
float: left;
padding: 1px;
- padding-left: 5px;
- padding-right: 5px;
- color: black;
- background-color: #A1BbA1;
- text-align: center;
margin: 2px;
}
-
- .meters {
- display: inline-block;
+ td > .disk_group {
+ display: grid;
+ grid-template-columns: auto auto auto auto auto;
+ padding-left: 0px;
+ padding-right: 0px;
+ margin: 1px;
}
+ td > .ram_group {
+ display: grid;
+ grid-template-columns: auto auto auto auto;
+ padding-left: 0px;
+ padding-right: 0px;
+ margin: 1px;
+ }
+ td > .vcpu_group {
+ display: grid;
+ grid-template-columns: auto;
+ padding-left: 0px;
+ padding-right: 0px;
+ margin: 1px;
+ }
+
+ .item {
+ display: inline-grid;
+ border-width: 1px;
+ border-style: solid;
+ margin: 1px 1px 1px 1px;
+ padding: 0px 1px 0px 1px;
+ }
+
+ .status_none { border-radius: 10px; width: 8px; }
+ .status_up { border-radius: 10px; width: 8px; background-color: #393; color: #393; }
+ .status_down { border-radius: 10px; width: 8px; background-color: #933; color: #933; }
- td.meters > .meter {
+ .head { height: 18px; }
+ .col_name { width: 250px; }
+ .col_role { width: 150px; }
+ .col_vendor { width: 100px; }
+ .col_release { width: 100px; }
+ .col_kernel { min-width: 100px; }
+ .col_vcpu { min-width: 40px; }
+ .col_ram { min-width: 150px; }
+ .col_disk { min-width: 200px; }
+
+ .col_notes { width: 618px; }
+ .meters {
+ display: inline-block;
+ margin: 1px;
+ }
+ .meters > .meter {
display: block;
float: left;
+ border-width: 1px;
+ border-style: solid;
+ margin: 0px 1px 0px 1px;
+ padding: 0px 1px 0px 1px;
+
}
+ .meters > .ok, .disk_group > .ok, .ram_group > .ok{
+ border-color: #80a080;
+ background-color: #efe;
+ }
+ .meters > .warn, .disk_group > .warn, .ram_group > .warn {
+ border-color: #d3a200;
+ background-color: rgb(255, 216, 133);
+ }
+ .meters > .fail, .disk_group > .fail, .ram_group > .fail {
+ border-color: #bb0000;
+ background-color: rgb(250, 135, 135);
+ }
+ .cpu { border-color: #a0c0a0; background-color: rgb(252, 248, 248); }
+ .ram { border-color: #c0c0a0; background-color: rgb(255, 255, 251); }
+ .disk { border-color: #cedfdf; background-color: rgb(237, 241, 243); }
+
+ .map_grid {
+ display: grid;
+ grid-template-columns: auto auto auto auto auto auto auto auto auto auto;
+ grid-column-gap: 20px;
+ padding-left: 0px;
+ padding-right: 0px;
+ margin: 1px;
+ margin-left: 20px;
+
+ }
+ .map_item {
+ display: inline-grid;
+ border-width: 0px;
+ border-style: solid;
+ margin: 1px 1px 1px 1px;
+ padding: 0px 1px 0px 1px;
+ }
+
+ .map_grid > .ok {
+ color: #80a080;
+ }
+ .map_grid > .warn {
+ color: #d3a200;
+ }
+ .map_grid > .fail {
+ color: #bb0000;
+ }
+
+ .services {
+ font-family: "LaoSangamMN", Monaco, monospace;
+ font-size: 1.1em;
+ background-color: white;
+ }
+ .service_node {
+ background-color: #ddd;
+ margin-bottom: 2px;
+ }
+ .service_grid {
+ display: grid;
+ grid-template-columns: repeat(8, auto);
+ grid-template-rows: repeat(10, auto);
+ grid-auto-flow: column;
+ grid-column-gap: 20px;
+ padding-left: 0px;
+ padding-right: 0px;
+ margin: 1px;
+ margin-left: 20px;
+ }
+ .service {
+ display: inline-grid;
+ text-align: center;
+ border-width: 0px;
+ border-style: solid;
+ margin: 1px 1px 1px 1px;
+ padding: 0px 1px 0px 1px;
+ min-width: 150px;
+ border-radius: 10px;
+ }
+
+ .service_grid > .on {
+ background-color: #8c8;
+ }
+ .service_grid > .off {
+ background-color: #9aa;
+ }
+ .service_grid > .fail {
+ background-color: rgb(250, 135, 135);
+ }
+
</style>
</head>
<body onload="init()">
@@ -47,6 +193,10 @@
<div class="text">{{ openstack_release }}</div>
<div class="label">MCP Version:</div>
<div class="text">{{ mcp_release }}</div>
+ <div class="label">RAM % Warning/Critical:</div>
+ <div class="text">{{ const['ram_warn'] }}/{{ const['ram_critical'] }}</div>
+ <div class="label">Disk % Warning/Critical:</div>
+ <div class="text">{{ const['disk_warn'] }}/{{ const['disk_critical'] }}</div>
<div class="label date">generated on: {{ gen_date }}</div>
</div>
@@ -61,16 +211,84 @@
<h5>{{ caller() }}</h5>
<hr>
<table class="cluster_nodes">
+ <tr class="node">
+ <td class="status_none"></td>
+ <td class="head col_name">Name</td>
+ <td class="head col_role">Role</td>
+ <td class="head col_vendor">Virtual</td>
+ <td class="head col_release">Linux</td>
+ <td class="head col_kernel">Kernel</td>
+
+ <td class="head col_vcpu">
+ <div class="meters vcpu">
+ <div class="meter cpu">vCPU</div>
+ </div>
+ </td>
+ <td class="head col_ram">
+ <div class="meters">
+ <div class="meter ram">Total</div>
+ <div class="meter ram">Used</div>
+ <div class="meter ram">Free</div>
+ <div class="meter ram">Available</div>
+ </div>
+ </td>
+ <td class="head col_disk">
+ <div class="meters">
+ <div class="meter disk">device</div>
+ <div class="meter disk">total</div>
+ <div class="meter disk">used</div>
+ <div class="meter disk">free</div>
+ <div class="meter disk">percent</div>
+ </div>
+ </td>
+ </tr>
{% for node in nodes.keys() | sort %}
{% set _ndat = nodes[node] %}
- <tr class="node virt">
- <td class="status">{{ _ndat['status'] }}</td>
- <td class="name">{{ node }}</td>
- <td class="role">{{ _ndat['role'] }}</td>
- <td class="vendor">QEMU</td>
- <td class="meter cpu">vCPU: 12</td>
- <td class="meter ram">RAM: 500GB</td>
- <td class="meter disk">VDA: 150/140/135</td>
+ <tr class="node" onclick="toggleClassByID('info_{{ node }}')" id='info_{{ node }}_button'>
+ <td class="status_{{ _ndat['status'] | node_status_class }}">.</td>
+ <td class="head col_name">{{ node }}</td>
+ <td class="head col_role">{{ _ndat['role'] }}</td>
+ <td class="head col_vendor">{{ _ndat['virt_vendor'] }}/{{ _ndat['virt_mode'] }}/{{ _ndat['virt_type'] }}</td>
+ <td class="head col_release">{{ _ndat['linux_arch'] }}/{{ _ndat['linux_codename'] }}</td>
+ <td class="head col_kernel">{{ _ndat['kernel'] }}</td>
+ <td class="head col_vcpu">
+ <div class="meters vcpu">
+ <div class="meter cpu">{{ _ndat['cpus'] }}</div>
+ </div>
+ </td>
+ <td class="head col_ram">
+ <div class="ram_group">
+ <div class="item ram">{{ _ndat['ram_total'] }}</div>
+ <div class="item ram">{{ _ndat['ram_used'] }}</div>
+ <div class="item ram">{{ _ndat['ram_free'] }}</div>
+ <div class="item ram {{ _ndat['ram_status'] }}">{{ _ndat['ram_available'] }}</div>
+ </div>
+ </td>
+ <td class="head col_disk">
+ <div class="disk_group">
+ <div class="item disk">{{ _ndat['disk_max_dev'] }}</div>
+ {% for val in _ndat['disk'][_ndat['disk_max_dev']]['v'] %}
+ <div class="item disk {{ _ndat['disk'][_ndat['disk_max_dev']]['f'] }}">{{ val }}</div>
+ {% endfor %}
+ </div>
+ </td>
+ </tr>
+ <tr class="collapsable" id="info_{{ node }}">
+ <td class="status_none"></td>
+ <td class="col_notes" colspan="4">.</td>
+ <td class="col_kernel"></td>
+ <td class="col_vcpu"></td>
+ <td class="col_ram"></td>
+ <td class="col_disk">
+ <div class="disk_group">
+ {% for dev in _ndat['disk'].keys() | sort %}
+ <div class="item disk">{{ dev }}</div>
+ {% for val in _ndat['disk'][dev]['v'] %}
+ <div class="item disk {{ _ndat['disk'][dev]['f'] }}">{{ val }}</div>
+ {% endfor %}
+ {% endfor %}
+ </div>
+ </td>
</tr>
{% endfor %}
</table>
@@ -83,13 +301,29 @@
<h5>{{ caller() }}</h5>
<hr>
<table class="networks">
- <tr class="subnet">192.168.10.0/24</tr>
- <tr class="net collapsable">
- <td class="status">UP</td>
- <td class="name">cfg01.internal.net</td>
- <td class="ip">192.168.10.11</td>
- <td class="param">1500</td>
+ {% for net in map.keys() %}
+ <tr class="subnet" onclick="toggleClassByID('net_{{ net }}')" id="{{ net }}_net_button">
+ <td>{{ net }}</td>
</tr>
+ <tr class="collapsable" id="net_{{ net }}"><td>
+ <div class="map_grid">
+ {% for node in map[net].keys() | sort %}
+ {% for d in map[net][node] %}
+ <div class="map_item name">{{ node }}</div>
+ <div class="map_item interface {{ d['interface_error'] }}">{{ d['interface'] }}</div>
+ <div class="map_item note">{{ d['interface_note'] }}</div>
+ <div class="map_item ipaddr">{{ d['ip_address'] }}</div>
+ <div class="map_item ipaddr_type">{{ d['address_type'] }}</div>
+ <div class="map_item mtu {{ d['mtu_error'] }}">{{ d['rt_mtu'] }}</div>
+ <div class="map_item status {{ d['status_error'] }}">{{ d['status'] }}</div>
+ <div class="map_item gate {{ d['subnet_gateway_error'] }}">{{ d['subnet_gateway'] }}</div>
+ <div class="map_item gate">{{ d['default_gateway'] }}</div>
+ <div class="map_item error_note">{{ d['error_note'] }}</div>
+ {% endfor %}
+ {% endfor %}
+ </div>
+ </td></tr>
+ {% endfor %}
</table>
<hr>
</div>
@@ -99,6 +333,23 @@
<div id="{{ id_label }}" class="barcontent">
<h5>{{ caller() }}</h5>
<hr>
+ <div class="services">
+ {% for node in nodes.keys() | sort %}
+ <div class="service_node" onclick="toggleClassByID('svc_{{ node }}')" id="svc_{{ node }}_button">{{ node }}</div>
+ <div class="collapsable" id="svc_{{ node }}">
+ <div class="service_grid">
+ {% for service in nodes[node]['services'].keys() | sort -%}
+ {% if nodes[node]['services'][service] %}
+ <div class="service name on">{{ service }}</div>
+ {% else %}
+ <div class="service name off">{{ service }}</div>
+ {% endif %}
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ <hr>
</div>
{% endmacro %}
diff --git a/templates/pkg_versions_csv.j2 b/templates/pkg_versions_csv.j2
index c754f63..a6690a9 100644
--- a/templates/pkg_versions_csv.j2
+++ b/templates/pkg_versions_csv.j2
@@ -5,7 +5,7 @@
{% for action in p['results'][status].keys() | sort(reverse=true) %}
{% for node in p['results'][status][action].keys() | sort %}
{% set nd = p['results'][status][action][node] %}
-{{ id_label }},{{ pkg_name }},{{ node }},{{ status | make_status_label }},{{ action | make_action_label }},{{ nd['i'].version }},{{ nd['c'].version }},{{ p['r'].version }}
+{{ id_label }},{{ pkg_name }},{{ node }},{{ status | pkg_status_label }},{{ action | pkg_action_label }},{{ nd['i'].version }},{{ nd['c'].version }},{{ p['r'].version }}
{% endfor %}
{% endfor %}
{% endfor %}
diff --git a/templates/pkg_versions_html.j2 b/templates/pkg_versions_html.j2
index 91b21e7..aaedcbc 100644
--- a/templates/pkg_versions_html.j2
+++ b/templates/pkg_versions_html.j2
@@ -142,13 +142,13 @@
{% macro prettify_version(v) %}
<div class="version">
{% if v.epoch %}
- <div class="v_epoch {{ v.epoch_status | make_status_class }}">{{ v.epoch }}</div>
+ <div class="v_epoch {{ v.epoch_status | pkg_status_class }}">{{ v.epoch }}</div>
<div class="colon">:</div>
{% endif %}
- <div class="v_upstream {{ v.upstream_status | make_status_class }}">{{ v.upstream }}{{ v.upstream_rev }}</div>
+ <div class="v_upstream {{ v.upstream_status | pkg_status_class }}">{{ v.upstream }}{{ v.upstream_rev }}</div>
{% if v.debian %}
<div class="dash">-</div>
- <div class="v_debian {{ v.debian_status | make_status_class }}">{{ v.debian }}{{ v.debian_rev }}</div>
+ <div class="v_debian {{ v.debian_status | pkg_status_class }}">{{ v.debian }}{{ v.debian_rev }}</div>
{% endif %}
{{ caller() }}
</div>
@@ -161,9 +161,9 @@
<td class="app">{{ dat['desc']['app'] }}</td>
<td class="package_name">{{ pkg_name }}</td>
<td class="status_container" colspan="3">
- <div class="status {{ status_shown | make_status_class }}">{{ status_shown | make_status_label }}</div>
- {% if action_shown | make_action_label %}
- <div class="action {{ action_shown | make_action_class }}">{{ action_shown | make_action_label }}</div>
+ <div class="status {{ status_shown | pkg_status_class }}">{{ status_shown | pkg_status_label }}</div>
+ {% if action_shown | pkg_action_label %}
+ <div class="action {{ action_shown | pkg_action_class }}">{{ action_shown | pkg_action_label }}</div>
{% endif %}
</td>
</tr>
@@ -179,9 +179,9 @@
<td class="repo">{{ n_counter }}</td>
<td class="node_name">{{ node }}</td>
<td class="status_container">
- <div class="status {{ status | make_status_class }}">{{ status | make_status_label }}</div>
- {% if action | make_action_label %}
- <div class="action {{ action | make_action_class }}">{{ action | make_action_label }}</div>
+ <div class="status {{ status | pkg_status_class }}">{{ status | pkg_status_label }}</div>
+ {% if action | pkg_action_label %}
+ <div class="action {{ action | pkg_action_class }}">{{ action | pkg_action_label }}</div>
{% endif %}
</td>
<td class="installed">
@@ -195,7 +195,7 @@
<td class="release">
<div class="tooltip">
{{ dat['r'].version }}
- <pre class="repoinfotext">{{ dat['repos'] | make_repo_info }}</pre>
+ <pre class="repoinfotext">{{ dat['repos'] | pkg_repo_info }}</pre>
</div>
</td>
</tr>
@@ -295,13 +295,13 @@
<tr>
<td width="50%">
<div class="status_container">
- <div class="status {{ cs.ok | make_status_class }}">{{ cs.ok | make_status_label }}</div>
+ <div class="status {{ cs.ok | pkg_status_class }}">{{ cs.ok | pkg_status_label }}</div>
<div class="text">Installed and Candidate epoch:upstream version mach</div>
</div>
</td>
<td width="50%">
<div class="status_container">
- <div class="action {{ ca.na | make_action_class }}">{{ ca.na | make_action_label }} (no action)</div>
+ <div class="action {{ ca.na | pkg_action_class }}">{{ ca.na | pkg_action_label }} (no action)</div>
<div class="text">No action suggested</div>
</div>
</td>
@@ -309,13 +309,13 @@
<tr>
<td width="50%">
<div class="status_container">
- <div class="status {{ cs.up | make_status_class }}">{{ cs.up | make_status_label }}</div>
+ <div class="status {{ cs.up | pkg_status_class }}">{{ cs.up | pkg_status_label }}</div>
<div class="text">Installed version is newer that the one found in Repo (i.e. candidate) or Release notes recordset</div>
</div>
</td>
<td width="50%">
<div class="status_container">
- <div class="action {{ ca.up | make_action_class }}">{{ ca.up | make_action_label }}</div>
+ <div class="action {{ ca.up | pkg_action_class }}">{{ ca.up | pkg_action_label }}</div>
<div class="text">There is an upgrade possible for the package. But it is not strictly required action</div>
</div>
</td>
@@ -323,13 +323,13 @@
<tr>
<td width="50%">
<div class="status_container">
- <div class="status {{ cs.down | make_status_class }}">{{ cs.down | make_status_label }}</div>
+ <div class="status {{ cs.down | pkg_status_class }}">{{ cs.down | pkg_status_label }}</div>
<div class="text">Installed version is older that the one found in Repo (i.e. candidate) or Release notes recordset</div>
</div>
</td>
<td width="50%">
<div class="status_container">
- <div class="action {{ ca.need_up | make_action_class }}">{{ ca.need_up | make_action_label }}</div>
+ <div class="action {{ ca.need_up | pkg_action_class }}">{{ ca.need_up | pkg_action_label }}</div>
<div class="text">Package should be upgraded to match version either in repo or in Release notes</div>
</div>
</td>
@@ -337,13 +337,13 @@
<tr>
<td width="50%">
<div class="status_container">
- <div class="status {{ cs.err | make_status_class }}">{{ cs.err | make_status_label }}</div>
+ <div class="status {{ cs.err | pkg_status_class }}">{{ cs.err | pkg_status_label }}</div>
<div class="text">Installed version conflicts with a combination of Candidate and Release notes versions</div>
</div>
</td>
<td width="50%">
<div class="status_container">
- <div class="action {{ ca.need_down | make_action_class }}">{{ ca.need_down | make_action_label }}</div>
+ <div class="action {{ ca.need_down | pkg_action_class }}">{{ ca.need_down | pkg_action_label }}</div>
<div class="text">Package should be downgraded to match version either in repo or in Release notes</div>
</div>
</td>
@@ -353,7 +353,7 @@
</td>
<td width="50%">
<div class="status_container">
- <div class="action {{ ca.repo | make_action_class }}">{{ ca.repo | make_action_label }}</div>
+ <div class="action {{ ca.repo | pkg_action_class }}">{{ ca.repo | pkg_action_label }}</div>
<div class="text">Repo that is configured on the target node contains invalid version and should be updated</div>
</div>
</td>
@@ -362,55 +362,55 @@
<hr>
<h5>Versions status and Action combinations</h5>
<div class="status_container">
- <div class="status {{ cs.ok | make_status_class }}">{{ cs.ok | make_status_label }}</div>
- <div class="action {{ ca.na | make_action_class }}">{{ ca.na | make_action_label }} (no action)</div>
+ <div class="status {{ cs.ok | pkg_status_class }}">{{ cs.ok | pkg_status_label }}</div>
+ <div class="action {{ ca.na | pkg_action_class }}">{{ ca.na | pkg_action_label }} (no action)</div>
<div class="text">All versions are inline with each other</div>
</div>
<div class="status_container">
- <div class="status {{ cs.up | make_status_class }}">{{ cs.up | make_status_label }}</div>
- <div class="action {{ ca.na | make_action_class }}">{{ ca.na | make_action_label }} (no action)</div>
+ <div class="status {{ cs.up | pkg_status_class }}">{{ cs.up | pkg_status_label }}</div>
+ <div class="action {{ ca.na | pkg_action_class }}">{{ ca.na | pkg_action_label }} (no action)</div>
<div class="text">Installed version is newer that Cadidate, Release version - unknown or not tracked</div>
</div>
<div class="status_container">
- <div class="status {{ cs.ok | make_status_class }}">{{ cs.ok | make_status_label }}</div>
- <div class="action {{ ca.up | make_action_class }}">{{ ca.up | make_action_label }}</div>
+ <div class="status {{ cs.ok | pkg_status_class }}">{{ cs.ok | pkg_status_label }}</div>
+ <div class="action {{ ca.up | pkg_action_class }}">{{ ca.up | pkg_action_label }}</div>
<div class="text">Installed version is equal to Release, but there is newer in the repo</div>
</div>
<div class="status_container">
- <div class="status {{ cs.up | make_status_class }}">{{ cs.up | make_status_label }}</div>
- <div class="action {{ ca.up | make_action_class }}">{{ ca.up | make_action_label }}</div>
+ <div class="status {{ cs.up | pkg_status_class }}">{{ cs.up | pkg_status_label }}</div>
+ <div class="action {{ ca.up | pkg_action_class }}">{{ ca.up | pkg_action_label }}</div>
<div class="text">Installed version is newer than Release, and there is even newer in the repo</div>
</div>
<div class="status_container">
- <div class="status {{ cs.err | make_status_class }}">{{ cs.err | make_status_label }}</div>
- <div class="action {{ ca.need_up | make_action_class }}">{{ ca.need_up | make_action_label }}</div>
+ <div class="status {{ cs.err | pkg_status_class }}">{{ cs.err | pkg_status_label }}</div>
+ <div class="action {{ ca.need_up | pkg_action_class }}">{{ ca.need_up | pkg_action_label }}</div>
<div class="text">Installed version is older than Candidate and Release versions and must be upgraded</div>
</div>
<div class="status_container">
- <div class="status {{ cs.err | make_status_class }}">{{ cs.err | make_status_label }}</div>
- <div class="action {{ ca.need_down | make_action_class }}">{{ ca.need_down | make_action_label }}</div>
+ <div class="status {{ cs.err | pkg_status_class }}">{{ cs.err | pkg_status_label }}</div>
+ <div class="action {{ ca.need_down | pkg_action_class }}">{{ ca.need_down | pkg_action_label }}</div>
<div class="text">Unknown version installed, Release and Candidate versions are older</div>
</div>
<div class="status_container">
- <div class="status {{ cs.err | make_status_class }}">{{ cs.err | make_status_label }}</div>
- <div class="action {{ ca.repo | make_action_class }}">{{ ca.repo | make_action_label }}</div>
+ <div class="status {{ cs.err | pkg_status_class }}">{{ cs.err | pkg_status_label }}</div>
+ <div class="action {{ ca.repo | pkg_action_class }}">{{ ca.repo | pkg_action_label }}</div>
<div class="text">Installed and Candidate versions is older than release and repo must be updated</div>
</div>
<div class="status_container">
- <div class="status {{ cs.up | make_status_class }}">{{ cs.up | make_status_label }}</div>
- <div class="action {{ ca.repo | make_action_class }}">{{ ca.repo | make_action_label }}</div>
+ <div class="status {{ cs.up | pkg_status_class }}">{{ cs.up | pkg_status_label }}</div>
+ <div class="action {{ ca.repo | pkg_action_class }}">{{ ca.repo | pkg_action_label }}</div>
<div class="text">Candidate version in repo is older vs Release and both older vs Installed</div>
</div>
<div class="status_container">
- <div class="status {{ cs.ok | make_status_class }}">{{ cs.ok | make_status_label }}</div>
- <div class="action {{ ca.repo | make_action_class }}">{{ ca.repo | make_action_label }}</div>
+ <div class="status {{ cs.ok | pkg_status_class }}">{{ cs.ok | pkg_status_label }}</div>
+ <div class="action {{ ca.repo | pkg_action_class }}">{{ ca.repo | pkg_action_label }}</div>
<div class="text">Candidate version in Repo is older vs release, but release version installed</div>
</div>
<div class="status_container">
- <div class="status {{ cs.down | make_status_class }}">{{ cs.down | make_status_label }}</div>
- <div class="action {{ ca.repo | make_action_class }}">{{ ca.repo | make_action_label }}</div>
+ <div class="status {{ cs.down | pkg_status_class }}">{{ cs.down | pkg_status_label }}</div>
+ <div class="action {{ ca.repo | pkg_action_class }}">{{ ca.repo | pkg_action_label }}</div>
<div class="text">Both Candidate in repo and Installed older vs release</div>
</div>
<div class="status_container">
diff --git a/versions/repo.versions.tgz b/versions/repo.versions.tgz
index ea020f3..00c36f0 100644
--- a/versions/repo.versions.tgz
+++ b/versions/repo.versions.tgz
Binary files differ