Ceph info and report OSD configuration

 - Added OSD configuration gathering
 - Uniq values filtering

 Related-PROD: PROD-36669

Change-Id: I7a2ef144554d892d06c0f05f978a512ba156360a
diff --git a/cfg_checker/modules/ceph/__init__.py b/cfg_checker/modules/ceph/__init__.py
index 85d4273..3f726f8 100644
--- a/cfg_checker/modules/ceph/__init__.py
+++ b/cfg_checker/modules/ceph/__init__.py
@@ -165,7 +165,8 @@
     ceph_bench_parser.add_argument(
         '--offset-increment',
         metavar="offset_increment", default="500M",
-        help="Offset to be used in 'read' and 'write' modes if multiple jobs used"
+        help="Offset to be used in 'read' and 'write' modes if multiple jobs "
+             "used"
         "Default: '500M'"
     )
 
@@ -184,6 +185,7 @@
 
     logger_cli.info("# Collecting Ceph cluster information")
     ceph_info.gather_info()
+    ceph_info.gather_osd_configs()
 
     # Debug, enable if needed to debug report generation
     # without actuall data collecting each time
@@ -211,6 +213,7 @@
     # ceph_info.load_info()
     # end debug
     ceph_info.gather_info()
+    ceph_info.gather_osd_configs()
     ceph_info.get_transposed_latency_table()
     ceph_info.get_latest_health_readout()
     ceph_info.create_html_report(_filename)
diff --git a/cfg_checker/modules/ceph/info.py b/cfg_checker/modules/ceph/info.py
index f6d5758..2a23936 100644
--- a/cfg_checker/modules/ceph/info.py
+++ b/cfg_checker/modules/ceph/info.py
@@ -594,3 +594,63 @@
         )
 
         return
+
+    def gather_osd_configs(self):
+        _total_osd = len(self.ceph_info["ceph_osd_df"]["data"]["nodes"])
+        logger_cli.info(
+            "-> Gathering OSD configuration ({})".format(_total_osd)
+        )
+        # Shortcuts
+        # _c = self._safe_tools_cmd
+        _cj = self._safe_get_cmd_output_as_json
+        _progress = Progress(_total_osd)
+        _idx = 1
+        _cfgs = {}
+        for _osd in self.ceph_info["ceph_osd_df"]["data"]["nodes"]:
+            _progress.write_progress(_idx, note=_osd["name"])
+            _cfgs[_osd["name"]] = _cj(
+                "ceph config show-with-defaults -f json {}".format(
+                    _osd["name"]
+                )
+            )
+            _idx += 1
+        _progress.end()
+
+        # Process configs
+        _base = {}
+        _uniq = {}
+        logger_cli.info("-> Filtering config values")
+        _progress = Progress(_total_osd)
+        _idx = 1
+        for _osd, _data in _cfgs.items():
+            _progress.write_progress(_idx, note=_osd)
+            for _o in _data:
+                _name = _o.pop("name")
+                if not _o["value"]:
+                    _o["value"] = "-"
+                if _name not in _base:
+                    _base[_name] = _o
+                elif _base[_name]["value"] != _o["value"]:
+                    _progress.clearline()
+                    logger_cli.info(
+                        "...specific value for {} (src: '{}'): {}={}".format(
+                            _osd,
+                            _o["source"],
+                            _name,
+                            _o["value"]
+                        )
+                    )
+                    _uniq[_osd] = {
+                        _name: _o
+                    }
+            _idx += 1
+        _progress.end()
+        self._add_ceph_info_item(
+            "osd_config_data",
+            "OSD Configuration values",
+            {
+                "common": _base,
+                "uniq": _uniq
+            }
+        )
+        return
diff --git a/setup.py b/setup.py
index d409398..da4f628 100644
--- a/setup.py
+++ b/setup.py
@@ -38,7 +38,7 @@
 
 setup(
     name="mcp-checker",
-    version="0.67",
+    version="0.68",
     author="Alex Savatieiev",
     author_email="osavatieiev@mirantis.com",
     classifiers=[
diff --git a/templates/ceph_info_html.j2 b/templates/ceph_info_html.j2
index 9dca52a..ac4d83f 100644
--- a/templates/ceph_info_html.j2
+++ b/templates/ceph_info_html.j2
@@ -84,6 +84,8 @@
         }
         
         .row_content {
+			font-family: "LaoSangamMN", Monaco, monospace;
+			font-size: 0.8em;
             padding: 0 18px;
             background-color: white;
             max-height: 0;
@@ -138,6 +140,13 @@
             padding-right: 0px;
             margin: 1px;
         }
+        td > .osdconf_group {
+            display: inline-block;
+            grid-template-columns: repeat(3, auto);
+            padding-left: 0px;
+            padding-right: 0px;
+            margin: 1px;
+        }
         .item {
         	display: inline-grid;
   			border-width: 1px;
@@ -218,6 +227,9 @@
         .lat_apply { border-color: #a0c0c0; background-color: rgb(255, 250, 250); text-align: left;  width: 35px}
         .meta_name { border-color: #c4b890; background-color: #e7dbb6; text-align: left; width: 150px;}
         .meta_value { border-color: #c6c3ba;background-color: #d4d4d4; text-align: left; width: 480px;}
+        .conf_name { border-color: #c4b890; background-color: #e7dbb6; text-align: left; width: 295px; word-break: break-all;}
+        .conf_value { border-color: #c6c3ba;background-color: #d4d4d4; text-align: left; width: 280px; word-break: break-all;}
+        .conf_source { border-color: #c6c3ba;background-color: #a4a4a4; text-align: left; width: 50px;}
 
         .map_grid {
             display: grid;
@@ -334,6 +346,7 @@
         <button class="bar-item" onclick="openBar(event, 'status')">Status</button>
         <button class="bar-item" onclick="openBar(event, 'latency')">Latency</button>
         <button class="bar-item" onclick="openBar(event, 'crush')">CRUSH Map</button>
+        <button class="bar-item" onclick="openBar(event, 'osdconf')">OSD Conf</button>
         <button class="bar-item" onclick="openBar(event, 'mondump')">Monitors</button>
         <button class="bar-item" onclick="openBar(event, 'df')">Pools</button>
         <button class="bar-item" onclick="openBar(event, 'dfrados')">Rados</button>
@@ -630,6 +643,46 @@
 </div>
 {% endmacro %}
 
+<!-- OSD Configuration -->
+{% macro osdconf_page(info, id_label) %}
+<div id="{{ id_label }}" class="barcontent">
+    <h5>{{ caller() }}</h5>
+    <hr>
+    {% set cbase = info["osd_config_data"]["data"]["common"] %}
+    {% set cuniq = info["osd_config_data"]["data"]["uniq"] %}
+    <button type="button" class="row_button">{{ cbase | length }} common configuration values</button>
+    <div class="row_content">
+        <table class="ceph_status"><tbody>
+            <tr><td class="metadata">
+            {% for tname, tdata in cbase.items() %}
+                <div class="osdconf_group">
+                    <div class="item conf_name">{{ tname }}</div>
+                    <div class="item conf_value">{{ tdata["value"] }}</div>
+                    <div class="item conf_source">{{ tdata["source"] }}</div>
+                </div>
+            {% endfor %}
+            </td></tr>
+        </tbody></table>
+    </div>
+    {% for osdname in cuniq.keys() | sort %}
+    <button type="button" class="row_button">{{ osdname }}: {{ cuniq[osdname] | length }} uniq values</button>
+    <div class="row_content">
+        <table class="ceph_status"><tbody>
+            <tr><td class="metadata">
+            {% for tname, tdata in cuniq[osdname].items() %}
+                <div class="osdconf_group">
+                    <div class="item conf_name">{{ tname }}</div>
+                    <div class="item conf_value">{{ tdata["value"] }}</div>
+                    <div class="item conf_source">{{ tdata["source"] }}</div>
+                </div>
+            {% endfor %}
+            </td></tr>
+        </tbody></table>
+    </div>
+    {% endfor %}
+</div>
+{% endmacro %}
+
 <!-- Latency -->
 {% macro latency_page(lat, id_label) %}
 <div id="{{ id_label }}" class="barcontent">
@@ -942,6 +995,10 @@
     CRUSH map
 {% endcall %}
 
+{% call osdconf_page(info, "osdconf") %}
+    OSD configs
+{% endcall %}
+
 {% call latency_page(info['osd_latency_data'], "latency") %}
     Quick latency check for all OSDs
 {% endcall %}