#!/usr/bin/env python # Copyright 2012-2016 Skylable Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Nagios plugin to check upload-download latency of an SX Cluster.""" import argparse import io import logging import random import shlex import time import nagiosplugin import sxclient __version__ = '0.2.0' _log = logging.getLogger('nagiosplugin') CONTENT_LENGTH = 4096 SUFFIX_LENGTH = 8 SUFFIX_CHARACTERS = ( 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' '0123456789' ) class UploadLatency(nagiosplugin.Resource): def __init__( self, remote_path, cluster_name, key_path, cluster_address=None, is_secure=True, verify_ssl=True, port=None, delete_after=False, timeout=sxclient.controller.DEFAULT_REQUEST_TIMEOUT ): cluster = sxclient.Cluster( cluster_name, cluster_address, is_secure=is_secure, verify_ssl_cert=verify_ssl, port=port ) user_data = sxclient.UserData.from_key_path(key_path) self.sx = sxclient.SXController( cluster, user_data, request_timeout=timeout ) remote_path = remote_path.decode('utf-8') self.volume, self.filename = self.split_remote_path(remote_path) self.delete_after = delete_after def split_remote_path(self, path): if '/' not in path: raise ValueError('Invalid remote path: %s' % path) volume, filename = path.split('/', 1) if not volume or not filename: raise ValueError('Invalid remote path: %s' % path) return volume, filename def probe(self): content = self.generate_random_content() stream = io.BytesIO(content) _log.debug('Volume used for testing: %s' % self.volume) _log.debug('Test file name: %s' % self.filename) time_start = time.time() _log.info('Started uploading at %f' % time_start) try: uploader = sxclient.SXFileUploader(self.sx) uploader.upload_stream( self.volume, CONTENT_LENGTH, self.filename, stream ) downloader = sxclient.SXFileCat(self.sx) downloaded_content = downloader.get_file_content( self.volume, self.filename ) except sxclient.exceptions.SXClusterNonFatalError as exc: self._raise_connection_error(exc) time_end = time.time() _log.info('Ended downloading at %f' % time_end) latency = time_end - time_start if self.delete_after: try: self.sx.deleteFile.call(self.volume, self.filename) except sxclient.exceptions.SXClusterNonFatalError as exc: self._raise_connection_error(exc) _log.debug('Test file deleted') else: _log.debug('Test file was not deleted') yield nagiosplugin.Metric( 'latency', latency, min=0, uom='s' ) yield nagiosplugin.Metric( 'file contents identical', content == downloaded_content ) def _raise_connection_error( self, exc, msg='Cannot connect to the cluster: connection refused' ): if 'connection refused' in str(exc).lower(): err_msg = msg raise ConnectionError(err_msg) else: raise exc def generate_random_content(self): content = bytearray( random.getrandbits(8) for byte in range(CONTENT_LENGTH) ) return content class BooleanContext(nagiosplugin.Context): def __init__( self, name, fmt_metric="'{name}' is {valueunit}", result_cls=nagiosplugin.Result ): super(BooleanContext, self).__init__(name, fmt_metric, result_cls) def evaluate(self, metric, resource): if metric.value: return self.result_cls(nagiosplugin.state.Ok, metric=metric) else: return self.result_cls(nagiosplugin.state.Critical, metric=metric) class ConnectionError(Exception): ''' Should be raised in case of a connection problem or unavailability of a cluster or one of its nodes. ''' @nagiosplugin.guarded def main(): args = parse_arguments() check = nagiosplugin.Check( UploadLatency( args.remote_path, args.hostname, args.key_path, args.ip_addresses, args.is_secure, args.verify, args.port, delete_after=args.delete_after, timeout=args.timeout ), nagiosplugin.ScalarContext('latency', args.warning, args.critical), BooleanContext('file contents identical') ) check.main(verbose=args.verbose, timeout=args.timeout) def parse_arguments(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( '-V', '--version', action='version', version=' '.join(['%(prog)s', __version__]) ) parser.add_argument( '-H', '--hostname', metavar='NAME', required=True, help='name of the cluster' ) parser.add_argument( '-i', '--ip-addresses', metavar='ADDRESS', dest='ip_addresses', default=None, type=comma_separated_list, help='comma-separated list of SX hosts (IP addresses)' ) parser.add_argument( '-p', '--port', type=int, default=None, help='cluster destination port' ) parser.add_argument( '-k', '--key-path', required=True, dest='key_path', help="path to the file with user's authentication key" ) parser.add_argument( '--no-ssl', dest='is_secure', action='store_false', default=True, help="disable secure communication" ) parser.add_argument( '--no-verify', dest='verify', action='store_false', default=True, help="don't verify the SSL certificate" ) parser.add_argument( '--remote-path', required=True, dest='remote_path', help='path for the test file to be created by the plugin; ' 'should contain volume name as its first component' ) parser.add_argument( '--delete-after', action='store_true', default=False, dest='delete_after', help='delete the test file after the upload; ' 'it is not deleted by default' ) parser.add_argument( '-w', '--warning', metavar='RANGE', default='', help='return warning if upload-download latency is outside RANGE' ) parser.add_argument( '-c', '--critical', metavar='RANGE', default='', help='return critical if upload-download latency is outside RANGE' ) parser.add_argument( '-v', '--verbose', action='count', default=0, help='increase output verbosity (use up to 3 times)' ) parser.add_argument( '-t', '--timeout', type=int, metavar='TIMEOUT', default=10, help='set plugin timeout to %(metavar)s; ' 'it is %(default)s by default; ' 'set to 0 or less for no timeout' ) return parser.parse_args() def comma_separated_list(string): lexer = shlex.shlex(string, posix=True) lexer.whitespace = ',' lexer.whitespace_split = True elts = [token.decode('utf-8') for token in lexer] return elts if __name__ == '__main__': main()