From 8a3aed731ea961dca8938900c9313ec46666dba3 Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Tue, 10 Aug 2021 20:58:46 +0200
Subject: [PATCH] update project

---
 agent_based/checkpoint_raid.py      | 374 ++++++++++++++++++++++++++++
 checkpoint_raid.mkp                 | Bin 5262 -> 5674 bytes
 packages/checkpoint_raid            |  18 +-
 web/plugins/wato/checkpoint_raid.py |  41 ++-
 4 files changed, 411 insertions(+), 22 deletions(-)
 create mode 100644 agent_based/checkpoint_raid.py

diff --git a/agent_based/checkpoint_raid.py b/agent_based/checkpoint_raid.py
new file mode 100644
index 0000000..3208552
--- /dev/null
+++ b/agent_based/checkpoint_raid.py
@@ -0,0 +1,374 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# License: GNU General Public License v2
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2016-05-31
+#
+# monitor Check Point appliance soft RAID
+#
+# 2018-03-08: changed snmp scan function
+# 2018-03-13: code cleanup
+# 2018-03-15: more code cleanup
+# 2020-06-08: changed snmp-scan function
+# 2020-11-10: removed perfdata
+#             added wato option to configure 'ignore disks'. For example on the Smart-1 5150 (Array has 12 Disks,
+#             but only 6 Disks are in the default hardware
+# 2021-04-29: rename from checkpoint_soft_raid in to checkpoint_raid
+# 2021-08-10: rewritten for CMK 2.0
+#
+# sample snmpwalk (one volume / two disks)
+#
+#  .1.3.6.1.4.1.2620.1.6.7.7.1.1.1.1.0 = INTEGER: 1
+#  .1.3.6.1.4.1.2620.1.6.7.7.1.1.2.1.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.1.1.3.1.0 = INTEGER: 2
+#  .1.3.6.1.4.1.2620.1.6.7.7.1.1.4.1.0 = INTEGER: 2
+#  .1.3.6.1.4.1.2620.1.6.7.7.1.1.5.1.0 = INTEGER: 1952448512
+#  .1.3.6.1.4.1.2620.1.6.7.7.1.1.6.1.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.1.1.7.1.0 = INTEGER: 1
+#  .1.3.6.1.4.1.2620.1.6.7.7.1.1.8.1.0 = Gauge32: 931
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.1.1.0 = INTEGER: 1
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.1.2.0 = INTEGER: 2
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.2.1.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.2.2.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.3.1.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.3.2.0 = INTEGER: 1
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.4.1.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.4.2.0 = INTEGER: 1
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.5.1.0 = STRING: "ATA     "
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.5.2.0 = STRING: "ATA     "
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.6.1.0 = STRING: "ST1000NM0033-9ZM"
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.6.2.0 = STRING: "ST1000NM0033-9ZM"
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.7.1.0 = STRING: "SN04"
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.7.2.0 = STRING: "SN04"
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.8.1.0 = INTEGER: 1953525168
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.8.2.0 = INTEGER: 1953525168
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.9.1.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.9.2.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.10.1.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.10.2.0 = INTEGER: 0
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.11.1.0 = INTEGER: 100
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.11.2.0 = INTEGER: 100
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.12.1.0 = Gauge32: 931
+#  .1.3.6.1.4.1.2620.1.6.7.7.2.1.12.2.0 = Gauge32: 931
+#
+#
+# sample info
+#
+# [[[u'1', u'0', u'2', u'2', u'1952448512', u'0', u'1', u'931']],
+#  [[u'1', u'0', u'0', u'0', u'ATA     ', u'ST1000NM0033-9ZM', u'SN04', u'1953525168', u'0', u'0', u'100', u'931'],
+#   [u'2', u'0', u'1', u'1', u'ATA     ', u'ST1000NM0033-9ZM', u'SN04', u'1953525168', u'0', u'0', u'100', u'931']]]
+#
+#
+
+from dataclasses import dataclass
+from typing import List, Dict
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    DiscoveryResult,
+    CheckResult,
+    StringTable,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    register,
+    Service,
+    equals,
+    Result,
+    State,
+    SNMPTree,
+    startswith,
+    all_of,
+    any_of,
+)
+
+
+@dataclass
+class RaidDisk:
+    diskindex: int
+    diskvolumeid: str
+    diskid: int
+    diskvendor: str
+    diskproductid: str
+    diskrevision: str
+    diskmaxlba: int
+    diskstate: int
+    diskflags: str
+    disksyncstate: int
+    disksize: int
+
+
+@dataclass
+class RaidVolume:
+    volumeindex: int
+    volumetype: str
+    numofdisksonraid: int
+    volumemaxlba: int
+    volumestate: int
+    volumeflags: str
+    volumesize: int
+    disks: List[RaidDisk]
+   
+
+_raid_volume_type = {
+    0: 'RAID-0',
+    1: 'RAID-1E',
+    2: 'RAID-1',
+    3: 'RAID_10',
+    4: 'RAID-4',
+    5: 'RAID-5',
+    6: 'RAID-6',
+    7: 'RAID-60',
+    8: 'RAID-50',
+}
+
+
+_raid_volume_state = {
+    0: 'OPTIMAL',
+    1: 'DEGRADED',
+    2: 'FAILED',
+}
+
+_raid_volume_flags = {
+    0: 'NONE',
+    1: 'ENABLED',
+    2: 'QUIESCED',
+    3: 'RESYNC IN PROGRESS',
+    4: 'VOLUME INACTIVE',
+    5: 'NOT CONFIGURED',
+    6: 'USING INTERIM RECOVERY MODE',
+    7: 'READY FOR RECOVERY OPERATION',
+    8: 'WRONG PHYSICAL DRIVE WAS REPLACED',
+    9: 'A PHYSICAL DRIVE IS NOT PROPERLY CONNECTED',
+    10: 'HARDWARE IS OVER HEATING',
+    11: 'HARDWARE WAS OVERHEATED',
+    12: 'CURRENTLY EXPENDING',
+    13: 'NOT YET AVAILABLE',
+    14: 'QUEUED FOR EXPENSION',
+    15: 'MIGRATING',
+    16: 'IMPACTED',
+    17: 'OFFLINE',
+    18: 'CLEARING',
+}
+
+_raid_disk_state = {
+    0: 'ONLINE',
+    1: 'MISSING',
+    2: 'NOT COMPATIBLE',
+    3: 'DISC FAILED',
+    4: 'INITIALIZING',
+    5: 'OFFLINE REQUESTED',
+    6: 'FAILED REQUESTED',
+    7: 'UNCONFIGURED GOOD SPUN UP',
+    8: 'UNCONFIGURED GOOD SPUN DOWN',
+    9: 'UNCONFIGURED BAD',
+    10: 'HOTSPARE',
+    11: 'DRIVE OFFLINE',
+    12: 'REBUILD',
+    13: 'FAILED',
+    14: 'COPYBACK',
+    255: 'OTHER OFFLINE',
+}
+
+_raid_disk_flags = {
+    0: 'NONE',
+    1: 'OUT OF SYNC',
+    2: 'QUIESCED',
+    3: 'VERIFYING',
+    4: 'READY',
+}
+
+
+def _get_flags(flags, flags_description):
+    description = ''
+    flags = int(flags)
+    if flags == 0:
+        return flags_description.get(0)
+    else:
+        for x in range(0, len(flags_description)):
+            if flags & (1 << x) != 0:
+                if flags_description.get(x + 1) is not None:
+                    description += f'{flags_description.get(x + 1)},'
+        return description
+
+
+def parse_checkpoint_raid(string_table: List[StringTable]) -> Dict[str, RaidVolume]:
+
+    raw_volumes, raw_disks = string_table
+    volumes = {}
+    disks = []
+
+    for disk in raw_disks:
+        diskindex, diskvolumeid, diskid, disknumber, diskvendor, diskproductid, diskrevision, diskmaxlba, diskstate, \
+            diskflags, disksyncstate, disksize = disk
+
+        if diskindex.isdigit():
+            disks.append(RaidDisk(
+                diskindex=int(diskindex),
+                diskvolumeid=diskvolumeid,
+                diskid=int(disknumber),
+                diskvendor=diskvendor,
+                diskproductid=diskproductid,
+                diskrevision=diskrevision,
+                diskmaxlba=int(diskmaxlba),
+                diskstate=int(diskstate),
+                diskflags=_get_flags(diskflags, _raid_disk_flags),
+                disksyncstate=int(disksyncstate),
+                disksize=int(disksize)
+            ))
+
+    for volume in raw_volumes:
+        volumeindex, volumeid, volumetype, numofdisksonraid, volumemaxlba, volumestate, volumeflags, volumesize = volume
+
+        if volumeindex.isdigit():
+            volumes[volumeid] = RaidVolume(
+                volumeindex=int(volumeindex),
+                volumetype=_raid_volume_type.get(int(volumetype), f'unknown ({volumetype}'),
+                numofdisksonraid=int(numofdisksonraid),
+                volumemaxlba=int(volumemaxlba),
+                volumestate=int(volumestate),
+                volumeflags=_get_flags(volumeflags, _raid_volume_flags),
+                volumesize=int(volumesize),
+                disks=[]
+            )
+            for disk in disks:
+                if disk.diskvolumeid == volumeid:
+                    volumes[volumeid].disks.append(disk)
+
+    return volumes
+
+
+def discovery_checkpoint_raid(section: Dict[str, RaidVolume]) -> DiscoveryResult:
+    for key in section.keys():
+        yield Service(item=key)
+
+
+def check_checkpoint_raid(item, params, section: Dict[str, RaidVolume]) -> CheckResult:
+    ignore_disks = params['ignore_disks'].split(',')
+
+    try:
+        volume = section[item]
+    except KeyError:
+        yield Result(state=State.CRIT, summary='Volume not found in SNMP Data')
+        return
+
+    # volume infos
+
+    details = ''
+    details += f'Volume Type: {volume.volumetype}, '
+    details += f'# of Disks: {volume.numofdisksonraid}, '
+    details += f'Size: {volume.volumesize}GB, '
+    details += f'max LBA: {volume.volumemaxlba}, '
+    details += f'Flags: {volume.volumeflags}'
+
+    summary = ''
+    summary += f'{volume.volumetype}, '
+    summary += f'{volume.numofdisksonraid} disks, '
+    summary += f'{volume.volumesize}GB, '
+    summary += f'State: {_raid_volume_state.get(volume.volumestate)}'
+
+    if volume.volumestate != 0:
+        yield Result(state=State.CRIT, summary=summary, details=details)
+    else:
+        yield Result(state=State.OK, summary=summary, details=details)
+
+    # disk infos
+    disks = volume.disks
+    disks_in_array = 0
+    disks_online = 0
+    disks_ignored = 0
+    for disk in disks:
+        diskid = disk.diskid
+        if not str(diskid) in ignore_disks:
+            disks_in_array += 1
+            syncstate = disk.disksyncstate
+            details = ''
+            details += f'Disk ID: {diskid}, '
+            details += f'State: {_raid_disk_state.get(disk.diskstate)}, '
+            details += f'Sync: {syncstate}%, '
+            details += f'Size: {disk.disksize}GB, '
+            if disk.diskmaxlba > 0:
+                details += f'max LBA: {disk.diskmaxlba}, '
+            if disk.diskvendor != 'UNKNOWN':
+                details += f'Vendor: {disk.diskvendor}, '
+            details += f'Product ID: {disk.diskproductid}, '
+            if disk.diskrevision != '':
+                details += f'Revision: {disk.diskrevision}, '
+            details += f'Flags: {disk.diskflags}'
+            if disk.diskstate != 0:
+                yield Result(state=State.CRIT,
+                             notice=f'Disk ID: {diskid}, state: {_raid_disk_state.get(disk.diskstate)}',
+                             details=details)
+            else:
+                yield Result(state=State.OK, notice=details,
+                             )
+                disks_online += 1
+        else:
+            disks_ignored += 1
+
+    if disks_in_array == disks_online:
+        yield Result(state=State.OK, summary=f'{disks_online}/{disks_in_array} Disks online')
+    else:
+        yield Result(state=State.CRIT, summary=f'{disks_online}/{disks_in_array} Disks online')
+
+    if disks_ignored > 0:
+        yield Result(state=State.OK, summary=f'{disks_ignored} Disks ignored')
+
+
+register.snmp_section(
+    name='checkpoint_raid',
+    parse_function=parse_checkpoint_raid,
+    fetch=[
+        SNMPTree(
+            base='.1.3.6.1.4.1.2620.1.6.7.7.1.1',  # CHECKPOINT-MIB::raidVolumeEntry
+            oids=[
+                '1',  # raidVolumeIndex
+                '2',  # raidVolumeID
+                '3',  # raidVolumeType
+                '4',  # numOfDisksOnRaid
+                '5',  # raidVolumeMaxLBA
+                '6',  # raidVolumeState
+                '7',  # raidVolumeFlags
+                '8',  # raidVolumeSize
+            ]
+        ),
+        SNMPTree(
+            base='.1.3.6.1.4.1.2620.1.6.7.7.2.1',  # CHECKPOINT-MIB::fwLSConnEntry
+            oids=[
+                '1',  # raidDiskIndex
+                '2',  # raidDiskVolumeID
+                '3',  # raidDiskID
+                '4',  # raidDiskNumber
+                '5',  # raidDiskVendor
+                '6',  # raidDiskProductID
+                '7',  # raidDiskRevision
+                '8',  # raidDiskMaxLBA
+                '9',  # raidDiskState
+                '10',  # raidDiskFlags
+                '11',  # raidDiskSyncState
+                '12',  # raidDiskSize
+            ]
+        )
+
+    ],
+    detect=any_of(
+        startswith('.1.3.6.1.2.1.1.2.0', '.1.3.6.1.4.1.2620'),
+        all_of(
+            equals('.1.3.6.1.2.1.1.2.0', '.1.3.6.1.4.1.8072.3.2.10'),
+            equals('.1.3.6.1.4.1.2620.1.6.1.0', 'SVN Foundation'),
+        )
+    )
+)
+
+register.check_plugin(
+    name='checkpoint_raid',
+    service_name='RAID ID %s',
+    discovery_function=discovery_checkpoint_raid,
+    check_function=check_checkpoint_raid,
+    check_default_parameters={
+        'ignore_disks': ''
+    },
+    check_ruleset_name='checkpoint_raid',
+)
diff --git a/checkpoint_raid.mkp b/checkpoint_raid.mkp
index 01cca1612a5ce8734bbf029ecd33a3b1e00e4ca8..6cca976e0c1b91dc244f8d685ac01c7dbc962fa7 100644
GIT binary patch
literal 5674
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

literal 5262
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
b;Q0TB^dBhynbyYRCJN0rwj6>sgogHiA|x)5

diff --git a/packages/checkpoint_raid b/packages/checkpoint_raid
index cb964e8..276d5db 100644
--- a/packages/checkpoint_raid
+++ b/packages/checkpoint_raid
@@ -1,14 +1,14 @@
-{'author': u'Th.L. (thl-cmk[at]outlook[dot]com)',
- 'description': u'monitor Check Point appliance RAID\n',
- 'download_url': 'http://thl-cmk.hopto.org/',
- 'files': {'checkman': ['checkpoint_raid'],
-           'checks': ['checkpoint_raid'],
+{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)',
+ 'description': 'monitor Check Point appliance RAID arrays\n',
+ 'download_url': 'https://thl-cmk.hopto.org',
+ 'files': {'agent_based': ['checkpoint_raid.py'],
+           'checkman': ['checkpoint_raid'],
            'web': ['plugins/metrics/checkpoint_raid.py',
                    'plugins/wato/checkpoint_raid.py']},
  'name': 'checkpoint_raid',
  'num_files': 4,
- 'title': u'Check Point appliance RAID',
- 'version': '20210429.v0.3',
- 'version.min_required': '1.2.8b8',
- 'version.packaged': '1.6.0p12',
+ 'title': 'Check Point appliance RAID',
+ 'version': '20210810.v0.4',
+ 'version.min_required': '2.0.0',
+ 'version.packaged': '2021.07.14',
  'version.usable_until': None}
\ No newline at end of file
diff --git a/web/plugins/wato/checkpoint_raid.py b/web/plugins/wato/checkpoint_raid.py
index 1f6ba24..a5cbe15 100644
--- a/web/plugins/wato/checkpoint_raid.py
+++ b/web/plugins/wato/checkpoint_raid.py
@@ -6,28 +6,43 @@
 # Author: thl-cmk[at]outlook[dot]com
 # URL   : https://thl-cmk.hopto.org
 #
-# Check_MK checkpoint_soft_rais WATO plugin
-#
-#
+# Check_MK checkpoint_raid WATO plugin
 #
+from cmk.gui.i18n import _
+from cmk.gui.valuespec import (
+    Dictionary,
+    TextAscii,
+)
+
+from cmk.gui.plugins.wato import (
+    CheckParameterRulespecWithItem,
+    rulespec_registry,
+    RulespecGroupCheckParametersNetworking,
+)
 
-register_check_parameters(
-    subgroup_networking,
-    'checkpoint_raid',
-    _('Check Point RAID'),
-    Dictionary(
+
+def _parameter_valuespec_checkpoint_raid():
+    return Dictionary(
         help=_(''),
         elements=[
             ('ignore_disks',
              TextUnicode(
                  title=_('list of comma separated disk IDs to ignore'),
-                 help=_('This is usefull for RAID Arrays with missing disks like on the Smart-1 5150. There the '
+                 help=_('This is useful for RAID Arrays with missing disks, like on the Smart-1 5150. There the '
                         'array is configured for 12 Disk but in the default Hardware are only 6 Disks.'),
                  default_value=None,
                  allow_empty=False,
              )),
         ],
-    ),
-    None,
-    match_type='dict',
-)
+    )
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithItem(
+        check_group_name='checkpoint_raid',
+        group=RulespecGroupCheckParametersNetworking,
+        item_spec=lambda: TextAscii(title=_('RAID ID'), ),
+        match_type='dict',
+        parameter_valuespec=_parameter_valuespec_checkpoint_raid,
+        title=lambda: _('Check Point RAID'),
+    ))
-- 
GitLab