US2925 add monitoring of host pNIC in OVS 01/41201/1
authoryayogev <yaronyogev@gmail.com>
Wed, 6 Sep 2017 17:17:57 +0000 (20:17 +0300)
committeryayogev <yaronyogev@gmail.com>
Wed, 6 Sep 2017 17:17:57 +0000 (20:17 +0300)
- check_interface.py: renamed to check_pnic_ovs.py
- check_interface.py: check changed to use 'ip link show' command
- monitor.py: handle check name where object type contains underscore,
  specifically 'host_pnic'
- monitor.py: refactored to work as class
- monitoring_pnic.py: changed to handle either OVS or VPP
- monitoring_check_handler.py: change to allow defining hard-coded
  check_type string for file_type naming
- monitoring config templates: removed default handler

Change-Id: Iad38fa108e9ceae18a7c94b3570a8d9b836a8632
Signed-off-by: yayogev <yaronyogev@gmail.com>
app/install/db/monitoring_config_templates.json
app/monitoring/checks/check_pnic_ovs.py [moved from app/monitoring/checks/check_interface.py with 61% similarity]
app/monitoring/handlers/handle_host_pnic.py [moved from app/monitoring/handlers/handle_pnic.py with 58% similarity]
app/monitoring/handlers/monitor.py
app/monitoring/setup/monitoring_check_handler.py
app/monitoring/setup/monitoring_pnic.py

index 3dbacae..9bddfa2 100644 (file)
@@ -88,8 +88,7 @@
                     "base"\r
                 ], \r
                 "handlers" : [\r
-                    "default", \r
-                    "file", \r
+                    "file",\r
                     "osdna-monitor"\r
                 ]\r
             }\r
         }, \r
         "keepalive" : {\r
             "handlers" : [\r
-                "default", \r
                 "file"\r
             ]\r
         }\r
                     "base"\r
                 ], \r
                 "handlers" : [\r
-                    "default", \r
-                    "file", \r
+                    "file",\r
                     "osdna-monitor"\r
                 ]\r
             }\r
                     "base"\r
                 ], \r
                 "handlers" : [\r
-                    "default", \r
-                    "file", \r
+                    "file",\r
                     "osdna-monitor"\r
                 ]\r
             }\r
                     "base"\r
                 ], \r
                 "handlers" : [\r
-                    "default", \r
-                    "file", \r
+                    "file",\r
                     "osdna-monitor"\r
                 ]\r
             }\r
                     "base"\r
                 ], \r
                 "handlers" : [\r
-                    "default", \r
-                    "file", \r
+                    "file",\r
                     "osdna-monitor"\r
                 ]\r
             }\r
     "monitoring_system" : "sensu", \r
     "type" : "client_check_link_vnic-vconnector.json"\r
 },\r
-{ \r
+{\r
     "side" : "client", \r
     "order" : "1", \r
     "condition" : {\r
         "mechanism_drivers" : [\r
-            "VPP"\r
+            "OVS"\r
         ]\r
     }, \r
     "config" : {\r
         "checks" : {\r
             "{objtype}_{objid}" : {\r
                 "interval" : 15, \r
-                "command" : "check_pnic_vpp.py", \r
+                "command" : "check_pnic_ovs.py {local_name}",\r
                 "standalone" : true,\r
                 "type": "metric",\r
                 "subscribers" : [\r
                     "base"\r
                 ], \r
                 "handlers" : [\r
-                    "default", \r
-                    "file", \r
+                    "file",\r
                     "osdna-monitor"\r
                 ]\r
             }\r
         }\r
     }, \r
     "monitoring_system" : "sensu", \r
-    "type" : "client_check_pnic.json"\r
+    "type" : "client_check_host_pnic_ovs.json"\r
+},\r
+{\r
+    "side" : "client",\r
+    "order" : "1",\r
+    "condition" : {\r
+        "mechanism_drivers" : [\r
+            "VPP"\r
+        ]\r
+    },\r
+    "config" : {\r
+        "checks" : {\r
+            "{objtype}_{objid}" : {\r
+                "interval" : 15,\r
+                "command" : "check_pnic_vpp.py",\r
+                "standalone" : true,\r
+                "type": "metric",\r
+                "subscribers" : [\r
+                    "base"\r
+                ],\r
+                "handlers" : [\r
+                    "file",\r
+                    "osdna-monitor"\r
+                ]\r
+            }\r
+        }\r
+    },\r
+    "monitoring_system" : "sensu",\r
+    "type" : "client_check_host_pnic_vpp.json"\r
 },\r
 { \r
     "side" : "client", \r
                     "base"\r
                 ], \r
                 "handlers" : [\r
-                    "default", \r
-                    "file", \r
+                    "file",\r
                     "osdna-monitor"\r
                 ]\r
             }\r
similarity index 61%
rename from app/monitoring/checks/check_interface.py
rename to app/monitoring/checks/check_pnic_ovs.py
index 4140dfe..c26e42f 100755 (executable)
@@ -9,13 +9,17 @@
 # http://www.apache.org/licenses/LICENSE-2.0                                  #
 ###############################################################################
 
-import re
 import sys
 import subprocess
 
 from binary_converter import binary2str
 
 
+def nic_not_found(name: str, output: str):
+    print("Error finding NIC {}{}{}\n".format(name, ': ' if output else '',
+                                              output))
+    return 2
+
 if len(sys.argv) < 2:
     print('name of interface must be specified')
     exit(2)
@@ -24,27 +28,18 @@ nic_name = str(sys.argv[1])
 rc = 0
 
 try:
-    out = subprocess.check_output(["ifconfig " + nic_name],
-                                  stderr=subprocess.STDOUT,
-                                  shell=True)
+    cmd = 'ip link show | grep -A1 "^[0-9]\+: {}:"'.format(nic_name)
+    out = subprocess.check_output([cmd], stderr=subprocess.STDOUT, shell=True)
     out = binary2str(out)
     lines = out.splitlines()
-    line_number = 1
-    line = -1
-    while line_number < len(lines):
-        line = lines[line_number]
-        if ' BROADCAST ' in line:
-            break
-        line_number += 1
-    state_match = re.match('^\W+([A-Z]+)', line)
-    if not state_match:
-        rc = 2
-        print('Error: failed to find status in ifconfig output: ' + out)
+    if not lines:
+        rc = nic_not_found(nic_name, '')
     else:
-        rc = 0 if state_match.group(1) == 'UP' else 2
+        line = lines[0]
+        if ' state UP ' not in line:
+            rc = 2
         print(out)
 except subprocess.CalledProcessError as e:
-    print("Error finding NIC {}: {}\n".format(nic_name, binary2str(e.output)))
-    rc = 2
+    rc = nic_not_found(nic_name, binary2str(e.output))
 
 exit(rc)
similarity index 58%
rename from app/monitoring/handlers/handle_pnic.py
rename to app/monitoring/handlers/handle_host_pnic.py
index 934bb16..3c5ba87 100644 (file)
 # handle monitoring event for pNIC objects
 
 from monitoring.handlers.monitoring_check_handler import MonitoringCheckHandler
+from utils.special_char_converter import SpecialCharConverter
 
-class HandlePnic(MonitoringCheckHandler):
 
-  def __init__(self, args):
-    super().__init__(args)
+class HandleHostPnic(MonitoringCheckHandler):
 
-  def handle(self, id, check_result):
-    object_id = id[:id.index('-')]
-    mac = id[id.index('-')+1:]
-    mac_address = '%s:%s:%s:%s:%s:%s' % \
-      (mac[0:2], mac[2:4], mac[4:6], mac[6:8], mac[8:10], mac[10:12])
-    object_id += '-' + mac_address
-    doc = self.doc_by_id(object_id)
-    if not doc:
-      return 1
-    self.keep_result(doc, check_result)
-    return check_result['status']
+    def __init__(self, args):
+        super().__init__(args)
+
+    def handle(self, obj_id, check_result):
+        object_id = obj_id[:obj_id.index('-')]
+        mac = obj_id[obj_id.index('-')+1:]
+        converter = SpecialCharConverter()
+        mac_address = converter.decode_special_characters(mac)
+        object_id += '-' + mac_address
+        doc = self.doc_by_id(object_id)
+        if not doc:
+            return 1
+        self.keep_result(doc, check_result)
+        return check_result['status']
index e147a7d..95ea4aa 100755 (executable)
@@ -15,78 +15,126 @@ import argparse
 import json
 import sys
 
+from utils.inventory_mgr import InventoryMgr
 from utils.mongo_access import MongoAccess
 from utils.util import ClassResolver
 
-DEFAULTS = {
-    'env': 'WebEX-Mirantis@Cisco',
-    'inventory': 'inventory',
-    'loglevel': 'WARNING'
-}
-
-
-
-def get_args():
-    parser = argparse.ArgumentParser()
-    parser.add_argument("-m", "--mongo_config", nargs="?", type=str,
-                        default="",
-                        help="name of config file with MongoDB server " +
-                        "access details")
-    parser.add_argument("-e", "--env", nargs="?", type=str,
-                        default=DEFAULTS['env'],
-                        help="name of environment to scan \n" +
-                        "(default: {})".format(DEFAULTS['env']))
-    parser.add_argument("-y", "--inventory", nargs="?", type=str,
-                        default=DEFAULTS['inventory'],
-                        help="name of inventory collection \n" +
-                        "(default: {}".format(DEFAULTS['inventory']))
-    parser.add_argument('-i', '--inputfile', nargs='?', type=str,
-                        default='',
-                        help="read input from the specifed file \n" +
-                        "(default: from stdin)")
-    parser.add_argument("-l", "--loglevel", nargs="?", type=str,
-                        default=DEFAULTS["loglevel"],
-                        help="logging level \n(default: '{}')"
-                        .format(DEFAULTS["loglevel"]))
-    args = parser.parse_args()
-    return args
-
-input = None
-args = get_args()
-MongoAccess.set_config_file(args.mongo_config)
-if args.inputfile:
-    try:
-        with open(args.inputfile, 'r') as input_file:
-            input = input_file.read()
-    except Exception as e:
-        raise FileNotFoundError("failed to open input file: " + args.inputfile)
-        exit(1)
-else:
-    input = sys.stdin.read()
-    if not input:
-        raise ValueError("No input provided on stdin")
-        exit(1)
-
-check_result_full = json.loads(input)
-check_client = check_result_full['client']
-check_result = check_result_full['check']
-check_result['id'] = check_result_full['id']
-name = check_result['name']
-status = check_result['status']
-object_type = name[:name.index('_')]
-object_id = name[name.index('_')+1:]
-if 'environment' in check_client:
-    args.env = check_client['environment']
-
-handler = None
-basic_handling_types = ['vedge', 'vservice']
-if object_type in basic_handling_types:
-    from monitoring.handlers.basic_check_handler import BasicCheckHandler
-    handler = BasicCheckHandler(args)
-else:
-    module_name = 'handle_' + object_type
-    handler = ClassResolver.get_instance_single_arg(args,
-                                                    module_name=module_name,
-                                                    package_name='monitoring.handlers')
-if handler:
-    handler.handle(object_id, check_result)
+
+class Monitor:
+    DEFAULTS = {
+        'env': 'WebEX-Mirantis@Cisco',
+        'inventory': 'inventory',
+        'loglevel': 'WARNING'
+    }
+    object_types = []
+
+    def __init__(self):
+        self.args = self.get_args()
+        MongoAccess.set_config_file(self.args.mongo_config)
+        self.inv = InventoryMgr()
+        self.inv.set_collections(self.args.inventory)
+        self.input_text = None
+
+    def get_args(self):
+        parser = argparse.ArgumentParser()
+        parser.add_argument("-m", "--mongo_config", nargs="?", type=str,
+                            default="",
+                            help="name of config file with MongoDB server " +
+                            "access details")
+        parser.add_argument("-e", "--env", nargs="?", type=str,
+                            default=self.DEFAULTS['env'],
+                            help="name of environment to scan \n" +
+                            "(default: {})".format(self.DEFAULTS['env']))
+        parser.add_argument("-y", "--inventory", nargs="?", type=str,
+                            default=self.DEFAULTS['inventory'],
+                            help="name of inventory collection \n" +
+                            "(default: {}".format(self.DEFAULTS['inventory']))
+        parser.add_argument('-i', '--inputfile', nargs='?', type=str,
+                            default='',
+                            help="read input from the specifed file \n" +
+                            "(default: from stdin)")
+        parser.add_argument("-l", "--loglevel", nargs="?", type=str,
+                            default=self.DEFAULTS["loglevel"],
+                            help="logging level \n(default: '{}')"
+                            .format(self.DEFAULTS["loglevel"]))
+        args = parser.parse_args()
+        return args
+
+    def get_object_types(self) -> list:
+        if not self.object_types:
+            docs = self.inv.find_items({'name': 'object_types'},
+                                       collection='constants')
+            for types_list in docs:
+                self.object_types = [t['value'] for t in types_list['data']]
+        if not self.object_types:
+            raise ValueError('Unable to fetch object types')
+        return self.object_types
+
+    def match_object_types(self, check_name: str) -> list:
+        object_types = self.get_object_types()
+        matches = [t for t in object_types if check_name.startswith(t + '_')]
+        return matches
+
+    def find_object_type_and_id(self, check_name: str):
+        # if we have multiple matching host types, then take the longest
+        # of these. For example, if matches are ['host', 'host_pnic'],
+        # then take 'host_pnic'.
+        # To facilitate this, we sort the matches by reverse order.
+        matching_object_types = sorted(self.match_object_types(check_name),
+                                       reverse=True)
+        if not matching_object_types:
+            raise ValueError('Unable to match check name "{}" with object type'
+                             .format(check_name))
+        obj_type = matching_object_types[0]
+        obj_id = check_name[len(obj_type)+1:]
+        return obj_type, obj_id
+
+    def read_input(self):
+        if self.args.inputfile:
+            try:
+                with open(self.args.inputfile, 'r') as input_file:
+                    self.input_text = input_file.read()
+            except Exception as e:
+                raise FileNotFoundError("failed to open input file {}: {}"
+                                        .format(self.args.inputfile, str(e)))
+        else:
+            self.input_text = sys.stdin.read()
+            if not self.input_text:
+                raise ValueError("No input provided on stdin")
+
+    def get_handler_by_type(self, obj_type):
+        module_name = 'handle_' + obj_type
+        package = 'monitoring.handlers'
+        handler = ClassResolver.get_instance_single_arg(self.args,
+                                                        module_name=module_name,
+                                                        package_name=package)
+        return handler
+
+    def get_handler(self, obj_type):
+        basic_handling_types = ['vedge', 'vservice']
+        if obj_type not in basic_handling_types:
+            return self.get_handler_by_type(obj_type)
+        from monitoring.handlers.basic_check_handler \
+            import BasicCheckHandler
+        return BasicCheckHandler(self.args)
+
+    def process_input(self):
+        check_result_full = json.loads(self.input_text)
+        check_client = check_result_full['client']
+        check_result = check_result_full['check']
+        check_result['id'] = check_result_full['id']
+        name = check_result['name']
+        object_type, object_id = monitor.find_object_type_and_id(name)
+        if 'environment' in check_client:
+            self.args.env = check_client['environment']
+
+        check_handler = self.get_handler(object_type)
+        if check_handler:
+            check_handler.handle(object_id, check_result)
+
+    def process_check_result(self):
+        self.read_input()
+        self.process_input()
+
+monitor = Monitor()
+monitor.process_check_result()
index 1c9a013..c453439 100644 (file)
@@ -25,7 +25,8 @@ class MonitoringCheckHandler(MonitoringHandler, SpecialCharConverter):
             host = self.inv.get_by_id(self.env, o['host'])
             if host and 'ip_address' in host:
                 self.replacements['client_ip'] = host['ip_address']
-        type_str = o['type'] if 'type' in o else 'link_' + o['link_type']
+        type_str = values['check_type'] if 'check_type' in values else \
+            (o['type'] if 'type' in o else 'link_' + o['link_type'])
         file_type = 'client_check_' + type_str + '.json'
         host = o['host']
         sub_dir = '/host/' + host
index fdc1c94..c1be96f 100644 (file)
@@ -17,4 +17,11 @@ class MonitoringPnic(MonitoringSimpleObject):
 
     # add monitoring setup for remote host
     def create_setup(self, o):
-        self.setup('host_pnic', o)
+        type = 'host_pnic'
+        env_config = self.configuration.get_env_config()
+        vpp_or_ovs = 'vpp' if 'VPP' in env_config['mechanism_drivers'] \
+            else 'ovs'
+        type_str = '{}_{}'.format(type, vpp_or_ovs)
+        self.setup(type, o, values={'check_type': type_str,
+                                    'local_name': o['local_name']})
+