aboutsummaryrefslogtreecommitdiffstats
path: root/app/monitoring
diff options
context:
space:
mode:
authorKoren Lev <korenlev@gmail.com>2017-12-18 19:16:16 +0200
committerKoren Lev <korenlev@gmail.com>2017-12-18 19:16:16 +0200
commit98c3ac7c859e34fe60d061b9ca591aba429e4118 (patch)
tree3ff2629def8938b12c0a0147e463e74e475c9032 /app/monitoring
parent4709d96cc240c0c4c5308d40361ca3c3da1152fd (diff)
release 1.2 + new tagging
Change-Id: I1e876451ec4a330f458dd57adadb15e39969b225 Signed-off-by: Koren Lev <korenlev@gmail.com>
Diffstat (limited to 'app/monitoring')
-rw-r--r--app/monitoring/checks/check_instance_communictions.py85
-rw-r--r--app/monitoring/checks/check_vconnector.py50
-rw-r--r--app/monitoring/handlers/handle_vconnector.py28
-rwxr-xr-xapp/monitoring/handlers/monitor.py83
-rw-r--r--app/monitoring/handlers/monitoring_check_handler.py4
-rw-r--r--app/monitoring/setup/monitoring_check_handler.py15
-rw-r--r--app/monitoring/setup/monitoring_host.py11
-rw-r--r--app/monitoring/setup/monitoring_instance.py67
-rw-r--r--app/monitoring/setup/monitoring_setup_manager.py4
-rw-r--r--app/monitoring/setup/monitoring_vconnector.py24
-rw-r--r--app/monitoring/setup/sensu_client_installer.py158
11 files changed, 522 insertions, 7 deletions
diff --git a/app/monitoring/checks/check_instance_communictions.py b/app/monitoring/checks/check_instance_communictions.py
new file mode 100644
index 0000000..d3a94b7
--- /dev/null
+++ b/app/monitoring/checks/check_instance_communictions.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+###############################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) #
+# and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+###############################################################################
+
+# find status of instance network
+# For each instance vNIC - take the MAC address
+# For each vService in the same network as the instance,
+# use local_service_id attribute in the following command in the network node:
+# "ip netns exec <local_service_id> arp -n"
+# look for the instance vNIC's mac_address to appear in the response
+# for each mac_address:
+# - if Flag 'C' = 'Complete' - mark result OK for that instance,
+# - 'I' = 'Incomplete' - mark as 'warn',
+# - no mac_address mark as 'error'
+
+import sys
+import subprocess
+
+from binary_converter import binary2str
+
+
+arp_headers = ['Address', 'HWtype', 'HWaddress', 'Flags', 'Mask', 'Iface']
+arp_mac_pos = arp_headers.index('HWaddress')
+arp_flags_pos = arp_headers.index('Flags')
+
+
+def check_vnic_tuple(vnic_and_service: str):
+ tuple_parts = vnic_and_service.split(',')
+ local_service_id = tuple_parts[0]
+ mac_address = tuple_parts[1]
+ check_output = None
+ try:
+ netns_cmd = 'ip netns exec {} arp -n'.format(local_service_id)
+ check_output = 'MAC={}, local_service_id={}\n'\
+ .format(mac_address, local_service_id)
+ netns_out = subprocess.check_output([netns_cmd],
+ stderr=subprocess.STDOUT,
+ shell=True)
+ netns_out = binary2str(netns_out)
+ check_output += '{}\n'.format(netns_out)
+ netns_lines = netns_out.splitlines()
+ if not netns_lines or \
+ netns_lines[0].endswith('No such file or directory'):
+ check_rc = 2
+ else:
+ mac_found = False
+ flags = None
+ for l in netns_lines:
+ line_parts = l.split()
+ line_mac = line_parts[arp_mac_pos]
+ if len(line_parts) > arp_mac_pos and line_mac == mac_address:
+ mac_found = True
+ flags = line_parts[arp_flags_pos]
+ break
+ if mac_found:
+ check_rc = 1 if flags == 'I' else 0
+ else:
+ check_rc = 2
+ except subprocess.CalledProcessError as e:
+ check_output = str(e)
+ check_rc = 2
+ return check_rc, check_output
+
+
+if len(sys.argv) < 2:
+ print('usage: ' + sys.argv[0] +
+ ' <vService local_service_id>,<MAC>[;<>,<>]...')
+ exit(1)
+
+rc = 0
+output = ''
+vnics = str(sys.argv[1]).split(';')
+for vnic_tuple in vnics:
+ tuple_ret, out = check_vnic_tuple(vnic_tuple)
+ rc = min(rc, tuple_ret)
+ output += out
+print(output)
+exit(rc)
diff --git a/app/monitoring/checks/check_vconnector.py b/app/monitoring/checks/check_vconnector.py
new file mode 100644
index 0000000..237a195
--- /dev/null
+++ b/app/monitoring/checks/check_vconnector.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+###############################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) #
+# and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+###############################################################################
+
+# find status of vconnector
+# vconnector object name defines name of bridge
+# use "brctl showmacs <bridge>", return ERROR if 'No such device' is returned
+
+import sys
+import subprocess
+
+from binary_converter import binary2str
+
+
+if len(sys.argv) < 2:
+ print('usage: ' + sys.argv[0] + ' <bridge>')
+ exit(1)
+bridge_name = str(sys.argv[1])
+
+rc = 0
+
+cmd = None
+out = ''
+try:
+ cmd = "brctl showmacs {}".format(bridge_name)
+ out = subprocess.check_output([cmd],
+ stderr=subprocess.STDOUT,
+ shell=True)
+ out = binary2str(out)
+ lines = out.splitlines()
+ if not lines or lines[0].endswith('No such device'):
+ rc = 2
+ else:
+ print(out)
+except subprocess.CalledProcessError as e:
+ rc = 2
+ out = str(e)
+
+if rc != 0:
+ print('Failed to find vConnector {}:\n{}\n'
+ .format(bridge_name, out))
+
+exit(rc)
diff --git a/app/monitoring/handlers/handle_vconnector.py b/app/monitoring/handlers/handle_vconnector.py
new file mode 100644
index 0000000..85ee05f
--- /dev/null
+++ b/app/monitoring/handlers/handle_vconnector.py
@@ -0,0 +1,28 @@
+###############################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) #
+# and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+###############################################################################
+# handle monitoring event for pNIC objects
+
+from monitoring.handlers.monitoring_check_handler import MonitoringCheckHandler
+from utils.special_char_converter import SpecialCharConverter
+
+
+class HandleVconnector(MonitoringCheckHandler):
+
+ 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']
diff --git a/app/monitoring/handlers/monitor.py b/app/monitoring/handlers/monitor.py
index 9caed74..2495110 100755
--- a/app/monitoring/handlers/monitor.py
+++ b/app/monitoring/handlers/monitor.py
@@ -12,11 +12,15 @@
# handle monitoring events
import argparse
+import datetime
import json
import sys
+from discover.configuration import Configuration
+from monitoring.handlers.monitoring_check_handler import MonitoringCheckHandler
from utils.inventory_mgr import InventoryMgr
from utils.mongo_access import MongoAccess
+from utils.special_char_converter import SpecialCharConverter
from utils.util import ClassResolver
@@ -32,7 +36,9 @@ class Monitor:
MongoAccess.set_config_file(self.args.mongo_config)
self.inv = InventoryMgr()
self.inv.set_collections(self.args.inventory)
+ self.configuration = Configuration()
self.input_text = None
+ self.converter = SpecialCharConverter()
def get_args(self):
parser = argparse.ArgumentParser()
@@ -125,13 +131,83 @@ class Monitor:
return handler
def get_handler(self, check_type, obj_type):
- basic_handling_types = ['vedge', 'vservice']
+ basic_handling_types = ['instance', 'vedge', 'vservice', 'vconnector']
if obj_type not in basic_handling_types:
return self.get_handler_by_type(check_type, obj_type)
from monitoring.handlers.basic_check_handler \
import BasicCheckHandler
return BasicCheckHandler(self.args)
+ def check_link_interdependency_for(self,
+ object_id: str,
+ from_type: str=None,
+ to_type: str=None):
+ if from_type is not None and to_type is not None or \
+ from_type is None and to_type is None:
+ raise ValueError('check_link_interdependency: '
+ 'supply one of from_type/to_type')
+ obj_id = self.converter.decode_special_characters(object_id)
+ obj = self.inv.get_by_id(environment=self.args.env, item_id=obj_id)
+ if not obj:
+ self.inv.log.error('check_link_interdependency: '
+ 'failed to find object with ID: {}'
+ .format(object_id))
+ return
+ if 'status' not in obj:
+ return
+ id_attr = 'source_id' if from_type is None else 'target_id'
+ link_type = '{}-{}'.format(
+ from_type if from_type is not None else obj['type'],
+ to_type if to_type is not None else obj['type'])
+ condition = {
+ 'environment': self.args.env,
+ 'link_type': link_type,
+ id_attr: obj_id
+ }
+ link = self.inv.find_one(search=condition, collection='links')
+ if not link:
+ self.inv.log.error('check_link_interdependency: '
+ 'failed to find {} link with {}: {}'
+ .format(link_type, id_attr, obj_id))
+ return
+ other_id_attr = '{}_id' \
+ .format('source' if from_type is not None else 'target')
+ other_obj = self.inv.get_by_id(environment=self.args.env,
+ item_id=link[other_id_attr])
+ if not other_obj:
+ self.inv.log.error('check_link_interdependency: '
+ 'failed to find {} with ID: {} (link type: {})'
+ .format(other_id_attr, link[other_id_attr],
+ link_type))
+ return
+ if 'status' not in other_obj:
+ return
+ status = 'Warning'
+ if obj['status'] == 'OK' and other_obj['status'] == 'OK':
+ status = 'OK'
+ elif obj['status'] == 'OK' and other_obj['status'] == 'OK':
+ status = 'OK'
+ link['status'] = status
+ time_format = MonitoringCheckHandler.TIME_FORMAT
+ timestamp1 = obj['status_timestamp']
+ t1 = datetime.datetime.strptime(timestamp1, time_format)
+ timestamp2 = other_obj['status_timestamp']
+ t2 = datetime.datetime.strptime(timestamp2, time_format)
+ timestamp = max(t1, t2)
+ link['status_timestamp'] = datetime.datetime.strftime(timestamp,
+ time_format)
+ self.inv.set(link, self.inv.collections['links'])
+
+ def check_link_interdependency(self, object_id: str, object_type: str):
+ conf = self.configuration.get_env_config()
+ if 'OVS' in conf['mechanism_drivers']:
+ if object_type == 'vedge':
+ self.check_link_interdependency_for(object_id,
+ to_type='host_pnic')
+ if object_type == 'host_pnic':
+ self.check_link_interdependency_for(object_id,
+ from_type='vedge')
+
def process_input(self):
check_result_full = json.loads(self.input_text)
check_client = check_result_full['client']
@@ -142,14 +218,19 @@ class Monitor:
monitor.find_object_type_and_id(name)
if 'environment' in check_client:
self.args.env = check_client['environment']
+ else:
+ raise ValueError('Check client should contain environment name')
+ self.configuration.use_env(self.args.env)
check_handler = self.get_handler(check_type, object_type)
if check_handler:
check_handler.handle(object_id, check_result)
+ self.check_link_interdependency(object_id, object_type)
def process_check_result(self):
self.read_input()
self.process_input()
+
monitor = Monitor()
monitor.process_check_result()
diff --git a/app/monitoring/handlers/monitoring_check_handler.py b/app/monitoring/handlers/monitoring_check_handler.py
index 1436a46..c1f70fb 100644
--- a/app/monitoring/handlers/monitoring_check_handler.py
+++ b/app/monitoring/handlers/monitoring_check_handler.py
@@ -21,13 +21,13 @@ from utils.logging.full_logger import FullLogger
from utils.special_char_converter import SpecialCharConverter
from utils.string_utils import stringify_datetime
-TIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z'
SOURCE_SYSTEM = 'Sensu'
ERROR_LEVEL = ['info', 'warn', 'error']
class MonitoringCheckHandler(SpecialCharConverter):
STATUS_LABEL = ['OK', 'Warning', 'Error']
+ TIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z'
def __init__(self, args):
super().__init__()
@@ -61,7 +61,7 @@ class MonitoringCheckHandler(SpecialCharConverter):
else status
if status_text:
doc['status_text'] = status_text
- doc['status_timestamp'] = strftime(TIME_FORMAT, timestamp)
+ doc['status_timestamp'] = strftime(self.TIME_FORMAT, timestamp)
if 'link_type' in doc:
self.inv.write_link(doc)
else:
diff --git a/app/monitoring/setup/monitoring_check_handler.py b/app/monitoring/setup/monitoring_check_handler.py
index c453439..d1b863d 100644
--- a/app/monitoring/setup/monitoring_check_handler.py
+++ b/app/monitoring/setup/monitoring_check_handler.py
@@ -8,7 +8,6 @@
# http://www.apache.org/licenses/LICENSE-2.0 #
###############################################################################
from monitoring.setup.monitoring_handler import MonitoringHandler
-from utils.inventory_mgr import InventoryMgr
from utils.special_char_converter import SpecialCharConverter
@@ -28,14 +27,13 @@ class MonitoringCheckHandler(MonitoringHandler, SpecialCharConverter):
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']
+ host = values['host'] if 'host' in values else o['host']
sub_dir = '/host/' + host
content = self.prepare_config_file(
file_type,
{'side': 'client', 'type': file_type})
# need to put this content inside client.json file
client_file = 'client.json'
- host = o['host']
client_file_content = self.get_config_from_db(host, client_file)
# merge checks attribute from current content into client.json
checks = client_file_content['config']['checks'] \
@@ -53,3 +51,14 @@ class MonitoringCheckHandler(MonitoringHandler, SpecialCharConverter):
}
content = client_file_content
self.write_config_file(client_file, sub_dir, host, content)
+
+ def get_check_from_db(self, o, postfix=''):
+ client_config = self.get_config_from_db(o. get('host', ''),
+ 'client.json')
+ if not client_config:
+ return {}
+ checks = client_config.get('config', {}).get('checks', {})
+ objid = self.encode_special_characters(o.get('id', ''))
+ object_check_id = '{}_{}{}'.format(o.get('type'), objid, postfix)
+ check = checks.get(object_check_id, {})
+ return check
diff --git a/app/monitoring/setup/monitoring_host.py b/app/monitoring/setup/monitoring_host.py
index 9450cf6..0b9f420 100644
--- a/app/monitoring/setup/monitoring_host.py
+++ b/app/monitoring/setup/monitoring_host.py
@@ -12,6 +12,7 @@ import os
from os.path import join, sep
from monitoring.setup.monitoring_handler import MonitoringHandler
+from monitoring.setup.sensu_client_installer import SensuClientInstaller
RABBITMQ_CONFIG_FILE = 'rabbitmq.json'
RABBITMQ_CONFIG_ATTR = 'rabbitmq'
@@ -27,13 +28,14 @@ class MonitoringHost(MonitoringHandler):
# add monitoring setup for remote host
def create_setup(self, o):
+ host_id = o.get('host', '')
+ self.install_sensu_on_host(host_id)
sensu_host_files = [
'transport.json',
'rabbitmq.json',
'client.json'
]
server_ip = self.env_monitoring_config['server_ip']
- host_id = o['host']
sub_dir = join('/host', host_id)
config = copy.copy(self.env_monitoring_config)
env_name = self.configuration.env_name
@@ -88,3 +90,10 @@ class MonitoringHost(MonitoringHandler):
# this configuration requires SSL
# keep the path of the files for later use
self.fetch_ssl_files.append(path)
+
+ def install_sensu_on_host(self, host_id):
+ auto_install = self.env_monitoring_config \
+ .get('install_monitoring_client', False)
+ if auto_install:
+ installer = SensuClientInstaller(self.env, host_id)
+ installer.install()
diff --git a/app/monitoring/setup/monitoring_instance.py b/app/monitoring/setup/monitoring_instance.py
new file mode 100644
index 0000000..b376441
--- /dev/null
+++ b/app/monitoring/setup/monitoring_instance.py
@@ -0,0 +1,67 @@
+###############################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) #
+# and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+###############################################################################
+from monitoring.setup.monitoring_simple_object import MonitoringSimpleObject
+
+
+class MonitoringInstance(MonitoringSimpleObject):
+
+ def __init__(self, env):
+ super().__init__(env)
+
+ # monitoring setup for instance can only be done after vNIC is found
+ # and network for vNIC is set, so the first call will not do anything
+ def create_setup(self, instance: dict):
+ vnics = self.inv.find_items({
+ 'environment': self.get_env(),
+ 'type': 'vnic',
+ 'vnic_type': 'instance_vnic',
+ 'id_path': {'$regex': '^{}/'.format(instance['id_path'])}
+ })
+ for vnic in vnics:
+ self.add_instance_communication_monitoring(instance, vnic)
+
+ # for instance we keep list of instance vNICs and services to use in call
+ # to check_instance_communications.py
+ # add this vNIC to the list with the corresponding
+ def add_instance_communication_monitoring(self, instance: dict, vnic: dict):
+ service = self.get_service_for_vnic(vnic)
+ if not service:
+ return
+ check = self.get_check_from_db(instance)
+ services_and_vnics = check.get('command', '')
+ if services_and_vnics:
+ services_and_vnics = \
+ services_and_vnics[services_and_vnics.index('.py')+4:]
+ services_and_vnics_list = \
+ services_and_vnics.split(';') if services_and_vnics \
+ else []
+ service_and_vnic = '{},{}'.format(service.get('local_service_id', ''),
+ vnic.get('id'))
+ if service_and_vnic in services_and_vnics_list:
+ return # we already have this tuple define
+ services_and_vnics_list.append(service_and_vnic)
+ values = {
+ 'objtype': 'instance',
+ 'objid': self.encode_special_characters(instance['id']),
+ 'host': service['host'],
+ 'services_and_vnics': ';'.join(services_and_vnics_list)
+ }
+ self.create_monitoring_for_object(instance, values)
+
+ def get_service_for_vnic(self, vnic: dict) -> dict:
+ services = self.inv.find_items({'environment': self.get_env(),
+ 'type': 'vservice',
+ 'network': vnic.get('network', '')})
+ if not services:
+ return {}
+ dhcp = next(s for s in services if s.get('service_type') == 'dhcp')
+ if dhcp:
+ return dhcp # If we have both DHCP and router, return the DHCP
+ return services[0] # currently only DHCP and router services
diff --git a/app/monitoring/setup/monitoring_setup_manager.py b/app/monitoring/setup/monitoring_setup_manager.py
index bc4fe01..8b7693a 100644
--- a/app/monitoring/setup/monitoring_setup_manager.py
+++ b/app/monitoring/setup/monitoring_setup_manager.py
@@ -11,12 +11,14 @@
from monitoring.setup.monitoring_handler import MonitoringHandler
from monitoring.setup.monitoring_host import MonitoringHost
+from monitoring.setup.monitoring_instance import MonitoringInstance
from monitoring.setup.monitoring_link_vnic_vconnector \
import MonitoringLinkVnicVconnector
from monitoring.setup.monitoring_pnic import MonitoringPnic
from monitoring.setup.monitoring_otep import MonitoringOtep
from monitoring.setup.monitoring_vedge import MonitoringVedge
from monitoring.setup.monitoring_vnic import MonitoringVnic
+from monitoring.setup.monitoring_vconnector import MonitoringVconnector
from monitoring.setup.monitoring_vservice import MonitoringVservice
@@ -31,7 +33,9 @@ class MonitoringSetupManager(MonitoringHandler):
"otep": MonitoringOtep(env),
"vedge": MonitoringVedge(env),
"host_pnic": MonitoringPnic(env),
+ "instance": MonitoringInstance(env),
"vnic": MonitoringVnic(env),
+ "vconnector": MonitoringVconnector(env),
"vservice": MonitoringVservice(env),
"vnic-vconnector": MonitoringLinkVnicVconnector(env)}
diff --git a/app/monitoring/setup/monitoring_vconnector.py b/app/monitoring/setup/monitoring_vconnector.py
new file mode 100644
index 0000000..9ddc6af
--- /dev/null
+++ b/app/monitoring/setup/monitoring_vconnector.py
@@ -0,0 +1,24 @@
+###############################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) #
+# and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+###############################################################################
+from monitoring.setup.monitoring_simple_object import MonitoringSimpleObject
+
+
+class MonitoringVconnector(MonitoringSimpleObject):
+
+ # add monitoring setup for remote host
+ def create_setup(self, o):
+ type = 'vconnector'
+ 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,
+ 'name': o['name']})
+
diff --git a/app/monitoring/setup/sensu_client_installer.py b/app/monitoring/setup/sensu_client_installer.py
new file mode 100644
index 0000000..72a8bbb
--- /dev/null
+++ b/app/monitoring/setup/sensu_client_installer.py
@@ -0,0 +1,158 @@
+###############################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) #
+# and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+###############################################################################
+import os.path
+from pkg_resources import parse_version
+
+from monitoring.setup.monitoring_handler import MonitoringHandler
+from utils.inventory_mgr import InventoryMgr
+
+
+class SensuClientInstaller(MonitoringHandler):
+
+ UBUNTU = 'ubuntu'
+ CENTOS = 'centos'
+
+ INSTALL_CMD = {
+ UBUNTU: 'dpkg -i {}',
+ CENTOS: 'rpm -i {}'
+ }
+ PERMISSIONS_CMD = {
+ UBUNTU: '',
+ CENTOS: 'usermod -aG wheel sensu'
+ }
+ SUDOERS_FILE = '/etc/sudoers'
+
+ available_downloads = {}
+
+ def __init__(self, env: str, host_id: str):
+ super().__init__(env)
+ self.cli_ssh = self.get_ssh(host_id)
+ self.inv = InventoryMgr()
+ self.host = self.inv.get_by_id(env, host_id)
+ self.server = self.env_monitoring_config.get('server_ip')
+ self.server_cli_ssh = self.get_ssh(self.server)
+ self.ubuntu_dist = None
+ self.required_package = None
+
+ def install(self):
+ pkg_to_install = self.get_pkg_to_install()
+ if not pkg_to_install:
+ return
+ try:
+ self.fetch_package(pkg_to_install)
+ self.install_package(pkg_to_install)
+ self.set_permissions()
+ except SystemError as e:
+ self.log.error('Sensu install on host {} failed: {}'
+ .format(self.host, str(e)))
+ return
+
+ @staticmethod
+ def get_attr_from_output(output_lines: list, attr: str) -> str:
+ matches = [l for l in output_lines if l.startswith(attr)]
+ if not matches:
+ return ''
+ line = matches[0]
+ return SensuClientInstaller.get_attr_from_output_line(line)
+
+ @staticmethod
+ def get_attr_from_output_line(output_line: str):
+ val = output_line[output_line.index(':')+1:].strip()
+ return val
+
+ INSTALLED = 'Installed: '
+ CANDIDATE = 'Candidate: '
+ SENSU_DIR = '/opt/sensu'
+ SENSU_PKG_DIR = '/etc/sensu/pkg'
+ SENSU_PKG_DIR_LOCAL = '/tmp/sensu_pkg'
+ SENSU_VERSION_FILE = '/opt/sensu/version-manifest.txt'
+
+ def find_available_downloads(self):
+ ls_output = self.server_cli_ssh.exec('ls -R {}'
+ .format(self.SENSU_PKG_DIR))
+ ls_lines = ls_output.splitlines()
+ last_target_dir = None
+ for line in ls_lines:
+ if line[-4:] in ['/32:', '/64:']:
+ last_target_dir = line.replace(self.SENSU_PKG_DIR, '')
+ continue
+ elif last_target_dir:
+ target_dir = last_target_dir.strip(os.path.sep).strip(':')
+ self.available_downloads[target_dir] = line
+ last_target_dir = None
+ else:
+ last_target_dir = None
+
+ def find_available_package(self, os_details: dict):
+ if not self.available_downloads:
+ self.find_available_downloads()
+ distribution = os_details['ID']
+ version = os_details['version'].split()[-2].lower()
+ arch = os_details['architecure'][-2:]
+ download_dir = os.path.join(distribution, version, arch)
+ download_file = self.available_downloads.get(download_dir)
+ full_path = '' if not download_file \
+ else os.path.join(self.SENSU_PKG_DIR, download_dir, download_file)
+ return download_file, full_path
+
+ @staticmethod
+ def find_available_version(download_file: str) -> str:
+ ver = download_file.replace('sensu', '').strip('-_')
+ ver = ver[:ver.index('-')]
+ return ver
+
+ def get_pkg_to_install(self) -> str:
+ if self.provision == self.provision_levels['none']:
+ return ''
+ if not self.host:
+ return ''
+ supported_os = [self.UBUNTU, self.CENTOS]
+ distribution = self.host['OS']['ID']
+ if distribution not in [self.UBUNTU, self.CENTOS]:
+ self.log.error('Sensu client auto-install only supported for: {}'
+ .format(', '.join(supported_os)))
+ return ''
+ cmd = 'if [ -d {} ]; then head -1 {} | sed "s/sensu //"; fi' \
+ .format(self.SENSU_DIR, self.SENSU_VERSION_FILE)
+ installed_version = self.cli_ssh.exec(cmd).strip()
+ os_details = self.host['OS']
+ available_pkg, pkg_path = self.find_available_package(os_details)
+ available_version = self.find_available_version(available_pkg)
+ if parse_version(available_version) <= parse_version(installed_version):
+ return ''
+ return pkg_path
+
+ def get_local_path(self, pkg_to_install: str):
+ return os.path.join(self.SENSU_PKG_DIR_LOCAL,
+ os.path.basename(pkg_to_install))
+
+ def fetch_package(self, pkg_to_install: str):
+ self.make_directory(self.SENSU_PKG_DIR_LOCAL)
+ self.get_file(self.server, pkg_to_install,
+ self.get_local_path(pkg_to_install))
+ local_path = self.get_local_path(pkg_to_install)
+ self.copy_to_remote_host(self.host['host'],
+ local_path=local_path,
+ remote_path=local_path)
+
+ def install_package(self, pkg_to_install):
+ local_path = self.get_local_path(pkg_to_install)
+ install_cmd = self.INSTALL_CMD[self.host['OS']['ID']]
+ self.cli_ssh.exec(install_cmd.format(local_path))
+
+ def set_permissions(self):
+ cmd = self.PERMISSIONS_CMD[self.host['OS']['ID']]
+ if cmd:
+ self.cli_ssh.exec(cmd)
+ # add to sudoers file
+ sudoer_permission = 'sensu ALL=(ALL) NOPASSWD: ALL'
+ sudoer_cmd = 'grep --silent -w sensu {} || echo "{}" >> {}'\
+ .format(self.SUDOERS_FILE, sudoer_permission, self.SUDOERS_FILE)
+ self.cli_ssh.exec(sudoer_cmd)