From 98c3ac7c859e34fe60d061b9ca591aba429e4118 Mon Sep 17 00:00:00 2001 From: Koren Lev Date: Mon, 18 Dec 2017 19:16:16 +0200 Subject: release 1.2 + new tagging Change-Id: I1e876451ec4a330f458dd57adadb15e39969b225 Signed-off-by: Koren Lev --- .../checks/check_instance_communictions.py | 85 +++++++++++ app/monitoring/checks/check_vconnector.py | 50 +++++++ app/monitoring/handlers/handle_vconnector.py | 28 ++++ app/monitoring/handlers/monitor.py | 83 ++++++++++- .../handlers/monitoring_check_handler.py | 4 +- app/monitoring/setup/monitoring_check_handler.py | 15 +- app/monitoring/setup/monitoring_host.py | 11 +- app/monitoring/setup/monitoring_instance.py | 67 +++++++++ app/monitoring/setup/monitoring_setup_manager.py | 4 + app/monitoring/setup/monitoring_vconnector.py | 24 ++++ app/monitoring/setup/sensu_client_installer.py | 158 +++++++++++++++++++++ 11 files changed, 522 insertions(+), 7 deletions(-) create mode 100644 app/monitoring/checks/check_instance_communictions.py create mode 100644 app/monitoring/checks/check_vconnector.py create mode 100644 app/monitoring/handlers/handle_vconnector.py create mode 100644 app/monitoring/setup/monitoring_instance.py create mode 100644 app/monitoring/setup/monitoring_vconnector.py create mode 100644 app/monitoring/setup/sensu_client_installer.py (limited to 'app/monitoring') 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 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] + + ' ,[;<>,<>]...') + 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 ", 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] + ' ') + 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) -- cgit 1.2.3-korg