From 56a138785fe0642484327dc328aeff0b944c0a6f Mon Sep 17 00:00:00 2001 From: Daniel Vrcic Date: Fri, 2 Oct 2015 15:38:21 +0200 Subject: [PATCH 01/15] only HTTPS endpoints allowed --- src/novaprobe.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/novaprobe.py b/src/novaprobe.py index 469f7ca..d35c060 100755 --- a/src/novaprobe.py +++ b/src/novaprobe.py @@ -108,6 +108,8 @@ def nagios_out(status, msg, retcode): def get_info(server, userca, capath, timeout): if server_ok(server, capath, timeout): o = urlparse(server) + if o.scheme != 'https': + nagios_out('Critical', 'Connection error %s - Probe expects HTTPS endpoint' % (o.scheme+'://'+o.netloc), 2) try: # fetch unscoped token token_suffix = '' From 6cb8445a4f7b0f57bde528e5cc549d8dbddc67d8 Mon Sep 17 00:00:00 2001 From: Daniel Vrcic Date: Fri, 2 Oct 2015 16:15:18 +0200 Subject: [PATCH 02/15] spec update --- nagios-plugins-fedcloud.spec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index 3cead20..bb2de6e 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -3,7 +3,7 @@ Summary: Nagios plugins for EGI FedCloud services Name: nagios-plugins-fedcloud Version: 0.1.0 -Release: 2%{?dist} +Release: 3%{?dist} License: ASL 2.0 Group: Network/Monitoring Source0: %{name}-%{version}.tar.gz @@ -33,6 +33,8 @@ rm -rf $RPM_BUILD_ROOT %{dir} %changelog +* Fri Oct 2 2015 Daniel Vrcic - 0.1.0-3%{?dist} +- novaprobe: only HTTPS endpoints allowed * Wed Sep 23 2015 Daniel Vrcic - 0.1.0-2%{?dist} - cdmiprobe: handle case when endpoint disabled SSLv3 - novaprobe: added image and flavor cmd options From a24500fb88f98dae4a9624f13ef62e9aab33a130 Mon Sep 17 00:00:00 2001 From: Daniel Vrcic Date: Tue, 6 Oct 2015 15:24:47 +0200 Subject: [PATCH 03/15] novaprobe: debugging helper leftover removed --- nagios-plugins-fedcloud.spec | 4 +++- src/novaprobe.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index bb2de6e..b359136 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -3,7 +3,7 @@ Summary: Nagios plugins for EGI FedCloud services Name: nagios-plugins-fedcloud Version: 0.1.0 -Release: 3%{?dist} +Release: 4%{?dist} License: ASL 2.0 Group: Network/Monitoring Source0: %{name}-%{version}.tar.gz @@ -33,6 +33,8 @@ rm -rf $RPM_BUILD_ROOT %{dir} %changelog +* Fri Oct 6 2015 Daniel Vrcic - 0.1.0-4%{?dist} +- novaprobe: debugging helper leftover removed * Fri Oct 2 2015 Daniel Vrcic - 0.1.0-3%{?dist} - novaprobe: only HTTPS endpoints allowed * Wed Sep 23 2015 Daniel Vrcic - 0.1.0-2%{?dist} diff --git a/src/novaprobe.py b/src/novaprobe.py index d35c060..5b88f8e 100755 --- a/src/novaprobe.py +++ b/src/novaprobe.py @@ -272,7 +272,6 @@ class ArgHolder(object): print "Flavor %s, ID:%s" % (flavor, flavor_id) except (requests.exceptions.ConnectionError, requests.exceptions.Timeout, requests.exceptions.HTTPError) as e: - print 'yes' nagios_out('Critical', 'could not fetch flavor ID, endpoint does not correctly exposes available flavors: %s' % errmsg_from_excp(e), 2) except (AssertionError, IndexError, AttributeError) as e: nagios_out('Critical', 'could not fetch flavor ID, endpoint does not correctly exposes available flavors: %s' % str(e), 2) From 603c94e2ac3a0b3b0208cfd42d0d9a385b18eb87 Mon Sep 17 00:00:00 2001 From: Daniel Vrcic Date: Tue, 19 Jan 2016 21:41:02 +0100 Subject: [PATCH 04/15] remove deprecations in novaprobe and cdmiprobe --- nagios-plugins-fedcloud.spec | 4 +++- src/cdmiprobe.py | 14 +++++++------- src/novaprobe.py | 14 +++++++------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index b359136..1c22d4a 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -3,7 +3,7 @@ Summary: Nagios plugins for EGI FedCloud services Name: nagios-plugins-fedcloud Version: 0.1.0 -Release: 4%{?dist} +Release: 5%{?dist} License: ASL 2.0 Group: Network/Monitoring Source0: %{name}-%{version}.tar.gz @@ -33,6 +33,8 @@ rm -rf $RPM_BUILD_ROOT %{dir} %changelog +* Tue Jan 19 2016 Daniel Vrcic - 0.1.0-5%{?dist} +- remove Py2.6 deprecations in cdmiprobe and novaprobe * Fri Oct 6 2015 Daniel Vrcic - 0.1.0-4%{?dist} - novaprobe: debugging helper leftover removed * Fri Oct 2 2015 Daniel Vrcic - 0.1.0-3%{?dist} diff --git a/src/cdmiprobe.py b/src/cdmiprobe.py index 3ac4d46..c7400ca 100755 --- a/src/cdmiprobe.py +++ b/src/cdmiprobe.py @@ -34,20 +34,20 @@ DEFAULT_PORT = 443 def errmsg_from_excp(e): - if getattr(e, 'message', False): + if getattr(e, 'args', False): retstr = '' - if isinstance(e.message, list) or isinstance(e.message, tuple) \ - or isinstance(e.message, dict): - for s in e.message: + if isinstance(e.args, list) or isinstance(e.args, tuple) \ + or isinstance(e.args, dict): + for s in e.args: if isinstance(s, str): retstr += s + ' ' if isinstance(s, tuple) or isinstance(s, tuple): retstr += ' '.join(s) return retstr - elif isinstance(e.message, str): - return e.message + elif isinstance(e.args, str): + return e.args else: - for s in e.message: + for s in e.args: retstr += str(s) + ' ' return retstr else: diff --git a/src/novaprobe.py b/src/novaprobe.py index 5b88f8e..2607fa4 100755 --- a/src/novaprobe.py +++ b/src/novaprobe.py @@ -31,20 +31,20 @@ SERVER_NAME = 'cloudmonprobe-servertest' def errmsg_from_excp(e): - if getattr(e, 'message', False): + if getattr(e, 'args', False): retstr = '' - if isinstance(e.message, list) or isinstance(e.message, tuple) \ - or isinstance(e.message, dict): - for s in e.message: + if isinstance(e.args, list) or isinstance(e.args, tuple) \ + or isinstance(e.args, dict): + for s in e.args: if isinstance(s, str): retstr += s + ' ' if isinstance(s, tuple) or isinstance(s, tuple): retstr += ' '.join(s) return retstr - elif isinstance(e.message, str): - return e.message + elif isinstance(e.args, str): + return e.args else: - for s in e.message: + for s in e.args: retstr += str(s) + ' ' return retstr else: From 845d5cf2c3311bea49cea6f3d3319cfcac146613 Mon Sep 17 00:00:00 2001 From: Enol Fernandez Date: Thu, 12 May 2016 14:17:16 +0200 Subject: [PATCH 05/15] Check for string before other types --- src/cdmiprobe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cdmiprobe.py b/src/cdmiprobe.py index c7400ca..9a9337c 100755 --- a/src/cdmiprobe.py +++ b/src/cdmiprobe.py @@ -36,7 +36,9 @@ def errmsg_from_excp(e): if getattr(e, 'args', False): retstr = '' - if isinstance(e.args, list) or isinstance(e.args, tuple) \ + if isinstance(e.args, basestring): + return e.args + elif isinstance(e.args, list) or isinstance(e.args, tuple) \ or isinstance(e.args, dict): for s in e.args: if isinstance(s, str): @@ -44,8 +46,6 @@ def errmsg_from_excp(e): if isinstance(s, tuple) or isinstance(s, tuple): retstr += ' '.join(s) return retstr - elif isinstance(e.args, str): - return e.args else: for s in e.args: retstr += str(s) + ' ' From 76106d481cd9e253adbe216640daa2e61b7562e2 Mon Sep 17 00:00:00 2001 From: Enol Fernandez Date: Thu, 12 May 2016 14:18:07 +0200 Subject: [PATCH 06/15] Do not assume "ops" in the tenant name --- src/cdmiprobe.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cdmiprobe.py b/src/cdmiprobe.py index 9a9337c..d0ccb75 100755 --- a/src/cdmiprobe.py +++ b/src/cdmiprobe.py @@ -171,6 +171,10 @@ def get_token(server, userca, capath, timeout): for t in tenants: if 'ops' in t['name']: tenant = t['name'] + break + else: + # if there is no "ops" tenant, use the first one + tenant = tenants[0]['name'] except(KeyError, IndexError): passed = False if v == len(HEADER_CDMI_VERSIONS) - 1: From c900fe54fabbbc49a076c9c49b299d20b700d6c9 Mon Sep 17 00:00:00 2001 From: Enol Fernandez Date: Thu, 12 May 2016 14:26:47 +0200 Subject: [PATCH 07/15] Do not retry auth depending on the CDMI version --- src/cdmiprobe.py | 182 +++++++++++++++++++++++------------------------ 1 file changed, 89 insertions(+), 93 deletions(-) diff --git a/src/cdmiprobe.py b/src/cdmiprobe.py index d0ccb75..a453c52 100755 --- a/src/cdmiprobe.py +++ b/src/cdmiprobe.py @@ -103,103 +103,92 @@ def nagios_out(status, msg, retcode): sys.stdout.write(status+": "+msg+"\n") sys.exit(retcode) -def get_token(server, userca, capath, timeout): - for v, cdmiver in enumerate(HEADER_CDMI_VERSIONS): - passed = True +def get_token(server, userca, capath, timeout, cdmiver): + try: + # initiate unauthorized response (HTTP 401) with keystone URL + headers, token = {}, None + headers.update(cdmiver) + headers.update({'Accept': '*/*'}) + response = requests.get(server, headers=headers, cert=userca, verify=False, timeout=timeout) + if response.status_code == 400: + response = requests.get(server, headers={}, cert=userca, verify=False) + except requests.exceptions.ConnectionError as e: + nagios_out('Critical', 'Connection error %s - %s' % (server, errmsg_from_excp(e)), 2) + + try: + # extract public keystone URL from response + keystone_server = re.search("Keystone.*=[\s'\"]*([\w:/\-_\.]*)[\s*\'\"]*", response.headers['www-authenticate']).group(1) + if ':5000' not in keystone_server: + raise AttributeError + except(KeyError, IndexError, AttributeError): + raise Exception('Could not fetch keystone server from response: Key not found %s' % errmsg_from_excp(e)) + + if server_ok(keystone_server, capath, timeout): try: - # initiate unauthorized response (HTTP 401) with keystone URL - headers, token = {}, None + # fetch unscoped token + token_suffix = '' + if keystone_server.endswith("v2.0"): + token_suffix = token_suffix+'/tokens' + else: + token_suffix = token_suffix+'/v2.0/tokens' + + headers, payload, token = {}, {}, None headers.update(cdmiver) headers.update({'Accept': '*/*'}) - response = requests.get(server, headers=headers, cert=userca, verify=False, timeout=timeout) - if response.status_code == 400: - response = requests.get(server, headers={}, cert=userca, verify=False) - except requests.exceptions.ConnectionError as e: - nagios_out('Critical', 'Connection error %s - %s' % (server, errmsg_from_excp(e)), 2) - - try: - # extract public keystone URL from response - keystone_server = re.search("Keystone.*=[\s'\"]*([\w:/\-_\.]*)[\s*\'\"]*", response.headers['www-authenticate']).group(1) - if ':5000' not in keystone_server: - raise AttributeError - except(KeyError, IndexError, AttributeError): - passed = False - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'Could not fetch keystone server from response: Key not found %s' % errmsg_from_excp(e), 2) - - if server_ok(keystone_server, capath, timeout): - try: - # fetch unscoped token - token_suffix = '' - if keystone_server.endswith("v2.0"): - token_suffix = token_suffix+'/tokens' - else: - token_suffix = token_suffix+'/v2.0/tokens' - - headers, payload, token = {}, {}, None - headers.update(cdmiver) - headers.update({'Accept': '*/*'}) - headers = {'content-type': 'application/json', 'accept': 'application/json'} - payload = {'auth': {'voms': True}} - response = requests.post(keystone_server+token_suffix, headers=headers, - data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) - response.raise_for_status() - token = response.json()['access']['token']['id'] - except(KeyError, IndexError): - passed = False - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'Could not fetch unscoped keystone token from response: Key not found %s' % errmsg_from_excp(e), 2) - except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'Connection error %s - %s' % (keystone_server+token_suffix, errmsg_from_excp(e)), 2) + headers = {'content-type': 'application/json', 'accept': 'application/json'} + payload = {'auth': {'voms': True}} + response = requests.post(keystone_server+token_suffix, headers=headers, + data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) + response.raise_for_status() + token = response.json()['access']['token']['id'] + except(KeyError, IndexError) as e: + raise Exception('Could not fetch unscoped keystone token from response: Key not found %s' % errmsg_from_excp(e)) + except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: + nagios_out('Critical', 'Connection error %s - %s' % (keystone_server+token_suffix, errmsg_from_excp(e)), 2) - try: - # use unscoped token to get a list of allowed tenants mapped to - # ops VO from VOMS proxy cert - tenant_suffix= '' - if keystone_server.endswith("v2.0"): - tenant_suffix = tenant_suffix+'/tenants' - else: - tenant_suffix = tenant_suffix+'/v2.0/tenants' - headers = {'content-type': 'application/json', 'accept': 'application/json'} - headers.update({'x-auth-token': token}) - response = requests.get(keystone_server+tenant_suffix, headers=headers, - data=None, cert=userca, verify=False, timeout=timeout) - response.raise_for_status() - tenants = response.json()['tenants'] - tenant = '' - for t in tenants: - if 'ops' in t['name']: - tenant = t['name'] - break - else: - # if there is no "ops" tenant, use the first one - tenant = tenants[0]['name'] - except(KeyError, IndexError): - passed = False - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'could not fetch allowed tenants from response: Key not found %s' % errmsg_from_excp(e), 2) - except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'connection error %s - %s' % (keystone_server+tenant_suffix, errmsg_from_excp(e)), 2) + try: + # use unscoped token to get a list of allowed tenants mapped to + # ops VO from VOMS proxy cert + tenant_suffix= '' + if keystone_server.endswith("v2.0"): + tenant_suffix = tenant_suffix+'/tenants' + else: + tenant_suffix = tenant_suffix+'/v2.0/tenants' + headers = {'content-type': 'application/json', 'accept': 'application/json'} + headers.update({'x-auth-token': token}) + response = requests.get(keystone_server+tenant_suffix, headers=headers, + data=None, cert=userca, verify=False, timeout=timeout) + response.raise_for_status() + tenants = response.json()['tenants'] + tenant = '' + for t in tenants: + if 'ops' in t['name']: + tenant = t['name'] + break + else: + # if there is no "ops" tenant, use the first one + tenant = tenants[0]['name'] + except(KeyError, IndexError) as e: + raise Exception('could not fetch allowed tenants from response: Key not found %s' % errmsg_from_excp(e)) + except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: + nagios_out('Critical', 'connection error %s - %s' % (keystone_server+tenant_suffix, errmsg_from_excp(e)), 2) - try: - # get scoped token for allowed tenant - headers = {'content-type': 'application/json', 'accept': 'application/json'} - payload = {'auth': {'voms': True, 'tenantName': tenant}} - response = requests.post(keystone_server+token_suffix, headers=headers, - data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) - response.raise_for_status() - token = response.json()['access']['token']['id'] + try: + # get scoped token for allowed tenant + headers = {'content-type': 'application/json', 'accept': 'application/json'} + payload = {'auth': {'voms': True, 'tenantName': tenant}} + response = requests.post(keystone_server+token_suffix, headers=headers, + data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) + response.raise_for_status() + token = response.json()['access']['token']['id'] + except(KeyError, IndexError) as e: + raise Exception('Critical', 'could not fetch scoped keystone token for %s from response: Key not found %s' % (tenant, errmsg_from_excp(e))) + except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: + nagios_out('Critical', 'connection error %s - %s' % (keystone_server+token_suffix, errmsg_from_excp(e)), 2) - except(KeyError, IndexError): - passed = False - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'could not fetch scoped keystone token for %s from response: Key not found %s' % (tenant, errmsg_from_excp(e)), 2) - except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'connection error %s - %s' % (keystone_server+token_suffix, errmsg_from_excp(e)), 2) + return token - if passed: - return token def main(): class ArgHolder(object): @@ -236,10 +225,17 @@ class ArgHolder(object): passed = True # fetch scoped token for ops VO - ks_token = get_token(argholder.endpoint, - argholder.cert, - argholder.capath, - argholder.timeout) + try: + ks_token = get_token(argholder.endpoint, + argholder.cert, + argholder.capath, + argholder.timeout, + cdmiver) + except Exception as e: + if v == len(HEADER_CDMI_VERSIONS) - 1: + nagios_out('Critical', e.message, 2) + continue + randstr = '-'+''.join(random.sample('abcdefghijklmno', 3)) randdata = ''.join(random.sample('abcdefghij1234567890', 20)) From 0dafa24e469be5175de71dd5e517ee4e82f6f5e9 Mon Sep 17 00:00:00 2001 From: Enol Fernandez Date: Fri, 13 May 2016 09:48:09 +0200 Subject: [PATCH 08/15] Put actions into functions and add a clean_up --- src/cdmiprobe.py | 192 ++++++++++++++++++++++++++--------------------- 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/src/cdmiprobe.py b/src/cdmiprobe.py index a453c52..55c721a 100755 --- a/src/cdmiprobe.py +++ b/src/cdmiprobe.py @@ -190,6 +190,81 @@ def get_token(server, userca, capath, timeout, cdmiver): return token +def create_container(argholder, ks_token, cdmiver, container_name): + # create container + headers, payload= {}, {} + headers.update(cdmiver) + headers.update({'accept': CDMI_CONTAINER, + 'content-type': CDMI_CONTAINER}) + headers.update({'x-auth-token': ks_token}) + response = requests.put(argholder.endpoint + container_name + '/', + headers=headers, cert=argholder.cert, verify=False) + response.raise_for_status() + + +def delete_container(argholder, ks_token, cdmiver, container_name): + # remove container + headers, payload= {}, {} + headers.update(cdmiver) + headers.update({'x-auth-token': ks_token}) + response = requests.delete(argholder.endpoint + container_name + '/', + headers=headers, cert=argholder.cert, verify=False) + response.raise_for_status() + + +def create_dataobject(argholder, ks_token, cdmiver, container_name, obj_name, + obj_data): + # create data object + headers, payload= {}, {} + headers.update(cdmiver) + headers.update({'accept': CDMI_OBJECT, + 'content-type': CDMI_OBJECT}) + headers.update({'x-auth-token': ks_token}) + payload = {'mimetype': 'text/plain'} + payload['value'] = unicode(obj_data) + payload['valuetransferencoding'] = 'utf-8' + response = requests.put(argholder.endpoint + container_name + obj_name, + data=json.dumps(payload), headers=headers, + cert=argholder.cert, verify=False) + response.raise_for_status() + + +def get_dataobject(argholder, ks_token, cdmiver, container_name, obj_name): + # get data object + headers, payload= {}, {} + headers.update(cdmiver) + headers.update({'accept': CDMI_OBJECT, + 'content-type': CDMI_OBJECT}) + headers.update({'x-auth-token': ks_token}) + response = requests.get(argholder.endpoint + container_name + obj_name, + headers=headers, cert=argholder.cert, verify=False) + response.raise_for_status() + return response.json()['value'] + + +def delete_dataobject(argholder, ks_token, cdmiver, container_name, obj_name): + # remove data object + headers, payload= {}, {} + headers.update(cdmiver) + headers.update({'x-auth-token': ks_token}) + response = requests.delete(argholder.endpoint + container_name + obj_name, + headers=headers, cert=argholder.cert, verify=False) + response.raise_for_status() + + +def clean_up(argholder, ks_token, cdmiver, container_name, obj_name=None): + if obj_name: + try: + delete_dataobject(argholder, ks_token, cdmiver, + container_name, obj_name) + except requests.exceptions.HTTPError as e: + sys.stderr.write('Clean up error: %s\n' % errmsg_from_excp(e)) + try: + delete_container(argholder, ks_token, cdmiver, container_name) + except requests.exceptions.HTTPError as e: + sys.stderr.write('Clean up error: %s\n' % errmsg_from_excp(e)) + + def main(): class ArgHolder(object): pass @@ -222,8 +297,6 @@ class ArgHolder(object): if server_ok(argholder.endpoint, argholder.capath, argholder.timeout): for v, cdmiver in enumerate(HEADER_CDMI_VERSIONS): - passed = True - # fetch scoped token for ops VO try: ks_token = get_token(argholder.endpoint, @@ -238,131 +311,76 @@ class ArgHolder(object): randstr = '-'+''.join(random.sample('abcdefghijklmno', 3)) + container_name = CONTAINER + randstr randdata = ''.join(random.sample('abcdefghij1234567890', 20)) + obj_name = DOBJECT + randstr try: - # create container - headers, payload= {}, {} - headers.update(cdmiver) - headers.update({'accept': CDMI_CONTAINER, - 'content-type': CDMI_CONTAINER}) - headers.update({'x-auth-token': ks_token}) - response = requests.put(argholder.endpoint+CONTAINER+randstr+'/', - headers=headers, cert=argholder.cert, verify=False) - response.raise_for_status() - + create_container(argholder, ks_token, cdmiver, container_name) except requests.exceptions.HTTPError as e: - passed = False if v == len(HEADER_CDMI_VERSIONS) - 1: nagios_out('Critical', 'test - create_container failed %s' % errmsg_from_excp(e), 2) + continue try: - # create data object - headers, payload= {}, {} - headers.update(cdmiver) - headers.update({'accept': CDMI_OBJECT, - 'content-type': CDMI_OBJECT}) - headers.update({'x-auth-token': ks_token}) - payload = {'mimetype': 'text/plain'} - payload['value'] = unicode(randdata) - payload['valuetransferencoding'] = 'utf-8' - response = requests.put(argholder.endpoint+CONTAINER+randstr+DOBJECT+randstr, - data=json.dumps(payload), headers=headers, - cert=argholder.cert, verify=False) - response.raise_for_status() - + create_dataobject(argholder, ks_token, cdmiver, container_name, + obj_name, randdata) except requests.exceptions.HTTPError as e: - passed = False + clean_up(argholder, ks_token, cdmiver, container_name) if v == len(HEADER_CDMI_VERSIONS) - 1: nagios_out('Critical', 'test - create_dataobject failed %s' % errmsg_from_excp(e), 2) + continue try: - # get data object - headers, payload= {}, {} - headers.update(cdmiver) - headers.update({'accept': CDMI_OBJECT, - 'content-type': CDMI_OBJECT}) - headers.update({'x-auth-token': ks_token}) - response = requests.get(argholder.endpoint+CONTAINER+randstr+DOBJECT+randstr, - headers=headers, cert=argholder.cert, verify=False) - response.raise_for_status() - if response.json()['value'] != randdata: + data = get_dataobject(argholder, ks_token, cdmiver, + container_name, obj_name) + if data != randdata: raise requests.exceptions.HTTPError('data integrity violated') - except requests.exceptions.HTTPError as e: - passed = False + clean_up(argholder, ks_token, cdmiver, container_name, obj_name) if v == len(HEADER_CDMI_VERSIONS) - 1: nagios_out('Critical', 'test - get_dataobject failed %s' % errmsg_from_excp(e), 2) + continue newranddata = ''.join(random.sample('abcdefghij1234567890', 20)) try: - # update data object - headers, payload= {}, {} - headers.update(cdmiver) - headers.update({'accept': CDMI_OBJECT, - 'content-type': CDMI_OBJECT}) - headers.update({'x-auth-token': ks_token}) - payload = {'mimetype': 'text/plain'} - payload['value'] = unicode(newranddata) - payload['valuetransferencoding'] = 'utf-8' - response = requests.put(argholder.endpoint+CONTAINER+randstr+DOBJECT+randstr, - data=json.dumps(payload), headers=headers, - cert=argholder.cert, verify=False) - response.raise_for_status() - + create_dataobject(argholder, ks_token, cdmiver, container_name, + obj_name, newranddata) except requests.exceptions.HTTPError as e: - passed = False + clean_up(argholder, ks_token, cdmiver, container_name, obj_name) if v == len(HEADER_CDMI_VERSIONS) - 1: nagios_out('Critical', 'test - update_dataobject failed %s' % errmsg_from_excp(e), 2) + continue try: - # get data object - headers, payload= {}, {} - headers.update(cdmiver) - headers.update({'accept': CDMI_OBJECT, - 'content-type': CDMI_OBJECT}) - headers.update({'x-auth-token': ks_token}) - response = requests.get(argholder.endpoint+CONTAINER+randstr+DOBJECT+randstr, - headers=headers, cert=argholder.cert, verify=False) - response.raise_for_status() - if response.json()['value'] != newranddata: + data = get_dataobject(argholder, ks_token, cdmiver, + container_name, obj_name) + if data != newranddata: raise requests.exceptions.HTTPError('data integrity violated') - except requests.exceptions.HTTPError as e: - passed = False + clean_up(argholder, ks_token, cdmiver, container_name, obj_name) if v == len(HEADER_CDMI_VERSIONS) - 1: nagios_out('Critical', 'test - get_dataobject failed %s' % errmsg_from_excp(e), 2) + continue try: - # remove data object - headers, payload= {}, {} - headers.update(cdmiver) - headers.update({'x-auth-token': ks_token}) - response = requests.delete(argholder.endpoint+CONTAINER+randstr+DOBJECT+randstr, - headers=headers, cert=argholder.cert, verify=False) - response.raise_for_status() - + delete_dataobject(argholder, ks_token, cdmiver, container_name, + obj_name) except requests.exceptions.HTTPError as e: - passed = False + clean_up(argholder, ks_token, cdmiver, container_name, obj_name) if v == len(HEADER_CDMI_VERSIONS) - 1: nagios_out('Critical', 'test - delete_dataobject failed %s' % errmsg_from_excp(e), 2) + continue try: - # remove container - headers, payload= {}, {} - headers.update(cdmiver) - headers.update({'x-auth-token': ks_token}) - response = requests.delete(argholder.endpoint+CONTAINER+randstr+'/', - headers=headers, cert=argholder.cert, verify=False) - response.raise_for_status() - + delete_container(argholder, ks_token, cdmiver, container_name) except requests.exceptions.HTTPError as e: - passed = False + clean_up(argholder, ks_token, cdmiver, container_name, obj_name) if v == len(HEADER_CDMI_VERSIONS) - 1: nagios_out('Critical', 'test - delete_container failed %s' % errmsg_from_excp(e), 2) + continue - if passed: - nagios_out('OK', 'container and dataobject creating, fetching and removing tests were successful', 0) + nagios_out('OK', 'container and dataobject creating, fetching and removing tests were successful', 0) main() From 97414f76596a1b27737ee35f3078764a4974902f Mon Sep 17 00:00:00 2001 From: Daniel Vrcic Date: Sun, 15 May 2016 09:35:18 +0200 Subject: [PATCH 09/15] cdmiprobe cleanup and easier spec ver handling --- nagios-plugins-fedcloud.spec | 5 ++ src/cdmiprobe.py | 133 ++++++++++++++++++----------------- 2 files changed, 72 insertions(+), 66 deletions(-) diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index 1c22d4a..367c449 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -33,6 +33,11 @@ rm -rf $RPM_BUILD_ROOT %{dir} %changelog +* Fri May 13 2016 Daniel Vrcic - 0.1.0-6%{?dist} +- cdmiprobe: add support for printing error msgs from packed exceptions +- cdmiprobe: wait some time before next operation +- cdmiprobe: fetched token implies that we have supported CDMI Specification version +- cdmiprobe: merged improvements with proper cleanup procedure by Enol Fernandez * Tue Jan 19 2016 Daniel Vrcic - 0.1.0-5%{?dist} - remove Py2.6 deprecations in cdmiprobe and novaprobe * Fri Oct 6 2015 Daniel Vrcic - 0.1.0-4%{?dist} diff --git a/src/cdmiprobe.py b/src/cdmiprobe.py index 55c721a..c8fd08c 100755 --- a/src/cdmiprobe.py +++ b/src/cdmiprobe.py @@ -16,7 +16,7 @@ import argparse, re, random, signal -import requests, sys, os, json, socket +import requests, sys, os, json, socket, time from OpenSSL.SSL import TLSv1_METHOD, Context, Connection from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT @@ -31,6 +31,8 @@ CONTAINER = '/container-probe' DOBJECT = '/dataobject-probe' +OPWAIT = 2 + DEFAULT_PORT = 443 def errmsg_from_excp(e): @@ -43,8 +45,10 @@ def errmsg_from_excp(e): for s in e.args: if isinstance(s, str): retstr += s + ' ' - if isinstance(s, tuple) or isinstance(s, tuple): + if isinstance(s, tuple): retstr += ' '.join(s) + if isinstance(s, Exception): + retstr = str(s) return retstr else: for s in e.args: @@ -53,6 +57,7 @@ def errmsg_from_excp(e): else: return str(e) + def server_ok(serverarg, capath, timeout): server_ctx = Context(TLSv1_METHOD) server_ctx.load_verify_locations(None, capath) @@ -99,10 +104,12 @@ def handler(signum, frame): return True + def nagios_out(status, msg, retcode): sys.stdout.write(status+": "+msg+"\n") sys.exit(retcode) + def get_token(server, userca, capath, timeout, cdmiver): try: # initiate unauthorized response (HTTP 401) with keystone URL @@ -296,6 +303,7 @@ class ArgHolder(object): nagios_out('Unknown', 'command-line arguments are not correct', 3) if server_ok(argholder.endpoint, argholder.capath, argholder.timeout): + ver = None for v, cdmiver in enumerate(HEADER_CDMI_VERSIONS): # fetch scoped token for ops VO try: @@ -307,80 +315,73 @@ class ArgHolder(object): except Exception as e: if v == len(HEADER_CDMI_VERSIONS) - 1: nagios_out('Critical', e.message, 2) - continue + # if we successfully fetched token, then we also have + # supported CDMI Specification version + ver = cdmiver - randstr = '-'+''.join(random.sample('abcdefghijklmno', 3)) - container_name = CONTAINER + randstr - randdata = ''.join(random.sample('abcdefghij1234567890', 20)) - obj_name = DOBJECT + randstr + randstr = '-'+''.join(random.sample('abcdefghijklmno', 3)) + container_name = CONTAINER + randstr + randdata = ''.join(random.sample('abcdefghij1234567890', 20)) + obj_name = DOBJECT + randstr - try: - create_container(argholder, ks_token, cdmiver, container_name) - except requests.exceptions.HTTPError as e: - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'test - create_container failed %s' % errmsg_from_excp(e), 2) - continue + try: + create_container(argholder, ks_token, ver, container_name) + except requests.exceptions.HTTPError as e: + nagios_out('Critical', 'test - create_container failed %s' % errmsg_from_excp(e), 2) - try: - create_dataobject(argholder, ks_token, cdmiver, container_name, - obj_name, randdata) - except requests.exceptions.HTTPError as e: - clean_up(argholder, ks_token, cdmiver, container_name) - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'test - create_dataobject failed %s' % errmsg_from_excp(e), 2) - continue + try: + create_dataobject(argholder, ks_token, ver, container_name, + obj_name, randdata) + except requests.exceptions.HTTPError as e: + clean_up(argholder, ks_token, ver, container_name) + nagios_out('Critical', 'test - create_dataobject failed %s' % errmsg_from_excp(e), 2) + time.sleep(OPWAIT) - try: - data = get_dataobject(argholder, ks_token, cdmiver, - container_name, obj_name) - if data != randdata: - raise requests.exceptions.HTTPError('data integrity violated') - except requests.exceptions.HTTPError as e: - clean_up(argholder, ks_token, cdmiver, container_name, obj_name) - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'test - get_dataobject failed %s' % errmsg_from_excp(e), 2) - continue + try: + data = get_dataobject(argholder, ks_token, ver, container_name, + obj_name) + if data != randdata: + raise requests.exceptions.HTTPError('data integrity violated') + except requests.exceptions.HTTPError as e: + clean_up(argholder, ks_token, ver, container_name, obj_name) + nagios_out('Critical', 'test - get_dataobject failed %s' % errmsg_from_excp(e), 2) + time.sleep(OPWAIT) - newranddata = ''.join(random.sample('abcdefghij1234567890', 20)) + newranddata = ''.join(random.sample('abcdefghij1234567890', 20)) - try: - create_dataobject(argholder, ks_token, cdmiver, container_name, - obj_name, newranddata) - except requests.exceptions.HTTPError as e: - clean_up(argholder, ks_token, cdmiver, container_name, obj_name) - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'test - update_dataobject failed %s' % errmsg_from_excp(e), 2) - continue - - try: - data = get_dataobject(argholder, ks_token, cdmiver, - container_name, obj_name) - if data != newranddata: - raise requests.exceptions.HTTPError('data integrity violated') - except requests.exceptions.HTTPError as e: - clean_up(argholder, ks_token, cdmiver, container_name, obj_name) - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'test - get_dataobject failed %s' % errmsg_from_excp(e), 2) - continue + try: + create_dataobject(argholder, ks_token, ver, container_name, + obj_name, newranddata) + except requests.exceptions.HTTPError as e: + clean_up(argholder, ks_token, ver, container_name, obj_name) + nagios_out('Critical', 'test - update_dataobject failed %s' % errmsg_from_excp(e), 2) + time.sleep(OPWAIT) - try: - delete_dataobject(argholder, ks_token, cdmiver, container_name, + try: + data = get_dataobject(argholder, ks_token, ver, container_name, obj_name) - except requests.exceptions.HTTPError as e: - clean_up(argholder, ks_token, cdmiver, container_name, obj_name) - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'test - delete_dataobject failed %s' % errmsg_from_excp(e), 2) - continue + if data != newranddata: + raise requests.exceptions.HTTPError('data integrity violated') + except requests.exceptions.HTTPError as e: + clean_up(argholder, ks_token, ver, container_name, obj_name) + nagios_out('Critical', 'test - get_dataobject failed %s' % errmsg_from_excp(e), 2) + time.sleep(OPWAIT) - try: - delete_container(argholder, ks_token, cdmiver, container_name) - except requests.exceptions.HTTPError as e: - clean_up(argholder, ks_token, cdmiver, container_name, obj_name) - if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', 'test - delete_container failed %s' % errmsg_from_excp(e), 2) - continue + try: + delete_dataobject(argholder, ks_token, ver, container_name, + obj_name) + except requests.exceptions.HTTPError as e: + clean_up(argholder, ks_token, ver, container_name, obj_name) + nagios_out('Critical', 'test - delete_dataobject failed %s' % errmsg_from_excp(e), 2) + time.sleep(OPWAIT) + + try: + delete_container(argholder, ks_token, ver, container_name) + except requests.exceptions.HTTPError as e: + clean_up(argholder, ks_token, ver, container_name, obj_name) + nagios_out('Critical', 'test - delete_container failed %s' % errmsg_from_excp(e), 2) - nagios_out('OK', 'container and dataobject creating, fetching and removing tests were successful', 0) + nagios_out('OK', 'container and dataobject creating, fetching and removing tests were successful', 0) main() From 0b3cd702f1d252f2c14ec2933435c3be86d6cbcb Mon Sep 17 00:00:00 2001 From: Daniel Vrcic Date: Mon, 16 May 2016 11:02:34 +0200 Subject: [PATCH 10/15] spec release bumped --- nagios-plugins-fedcloud.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index 367c449..399e9de 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -3,7 +3,7 @@ Summary: Nagios plugins for EGI FedCloud services Name: nagios-plugins-fedcloud Version: 0.1.0 -Release: 5%{?dist} +Release: 6%{?dist} License: ASL 2.0 Group: Network/Monitoring Source0: %{name}-%{version}.tar.gz From 9930ef8552b6ec4fb3b4962974814759a9a83dd9 Mon Sep 17 00:00:00 2001 From: eimamagi Date: Tue, 22 Nov 2016 10:44:55 +0100 Subject: [PATCH 11/15] Removed OCCI probe which is maintained by Boris. Moved probes to folder /usr/libexec/argo-monitoring/probes/fedcloud. --- nagios-plugins-fedcloud.spec | 8 +- src/check_occi_compute_create | 467 ---------------------------------- 2 files changed, 5 insertions(+), 470 deletions(-) delete mode 100755 src/check_occi_compute_create diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index 399e9de..6e35673 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -1,9 +1,9 @@ -%define dir %{_libdir}/nagios/plugins/fedcloud +%define dir /usr/libexec/argo-monitoring/probes/fedcloud Summary: Nagios plugins for EGI FedCloud services Name: nagios-plugins-fedcloud -Version: 0.1.0 -Release: 6%{?dist} +Version: 0.1.1 +Release: 7%{?dist} License: ASL 2.0 Group: Network/Monitoring Source0: %{name}-%{version}.tar.gz @@ -33,6 +33,8 @@ rm -rf $RPM_BUILD_ROOT %{dir} %changelog +* Tue Nov 22 2016 Emir Imamagic - 0.1.1-7%{?dist} +- Probes location aligned with guidelines * Fri May 13 2016 Daniel Vrcic - 0.1.0-6%{?dist} - cdmiprobe: add support for printing error msgs from packed exceptions - cdmiprobe: wait some time before next operation diff --git a/src/check_occi_compute_create b/src/check_occi_compute_create deleted file mode 100755 index 6dd69f7..0000000 --- a/src/check_occi_compute_create +++ /dev/null @@ -1,467 +0,0 @@ -#!/opt/occi-cli/embedded/bin/ruby - -# -------------------------------------------------------------------------- # -# 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. # -#--------------------------------------------------------------------------- # - -require 'rubygems' -require 'occi-api' -require 'optparse' -require 'uri' -require 'logger' -require 'openssl' -require 'timeout' - -extend Occi::Api::Dsl - -## aux class definitions -module Occi::Probe - VERSION = "0.0.3" - BASIC_KINDS = %w(compute network storage) - BASIC_MIXINS = %w(os_tpl resource_tpl) - CONTEXTUALIZATION_MIXINS = %w(http://schemas.openstack.org/instance/credentials#public_key http://schemas.openstack.org/compute/instance#user_data) -end - -module Occi::Probe::RetVals - OK = 0 - WARNING = 1 - CRITICAL = 2 - UNKNOWN = 3 -end - -module Occi::Probe::Errors - class Timeout < Timeout::Error; end - class ComputeErrored < RuntimeError; end -end - -module Occi::Probe - class Log < ::Occi::Log - SUBSCRIPTION_HANDLE = "rOCCI-probe.log" - - attr_reader :api_log - - def initialize(log_dev, log_prefix = '[rOCCI-probe]') - @api_log = ::Occi::Api::Log.new(log_dev) - super - end - - def close - super - @api_log.close - end - - # @param severity [::Logger::Severity] severity - def level=(severity) - @api_log.level = severity - super - end - - def core_log - @api_log.core_log - end - end -end - -## constants -AUTH_METHODS = [:x509, :basic, :digest, :none].freeze -MEDIA_TYPES = ["application/occi+json", "text/plain,text/occi", "text/plain", "text/occi"].freeze - -options = Hashie::Mash.new - -## defaults -options.debug = false - -options.log = {} -options.log.out = STDERR -options.log.level = Occi::Probe::Log::ERROR - -options.endpoint = "http://localhost:3000" -options.timeout = 180 - -options.auth = {} -options.auth.type = "none" -options.auth.user_cert = "#{ENV['HOME']}/.globus/usercred.pem" -options.auth.ca_path = "/etc/grid-security/certificates" -options.auth.username = "anonymous" -options.auth.ca_file = nil -options.auth.voms = false - -options.output_format = :plain - -options.attributes = [] -options.media_type = "text/plain,text/occi" - -options.resource = 'compute' -options.action = 'create' - -## parse arguments -opts = OptionParser.new do |opts| - opts.banner = %{Usage: check_occi_compute_create [OPTIONS]} - - opts.separator "" - opts.separator "Options:" - - opts.on("-e", - "--endpoint URI", - String, - "OCCI server URI, defaults to #{options.endpoint.inspect}") do |endpoint| - options.endpoint = URI(endpoint).to_s - end - - opts.on("-n", - "--auth METHOD", - AUTH_METHODS, - "Authentication method, only: [#{AUTH_METHODS.join('|')}], defaults " \ - "to #{options.auth.type.inspect}") do |auth| - options.auth.type = auth.to_s - end - - opts.on("-t", - "--timeout SEC", - Integer, - "Default timeout for all HTTP connections, in seconds") do |timeout| - raise "Timeout has to be a number larger than 0!" if timeout < 1 - options.timeout = timeout - end - - opts.on("-u", - "--username USER", - String, - "Username for basic or digest authentication, defaults to " \ - "#{options.auth.username.inspect}") do |username| - options.auth.username = username - end - - opts.on("-p", - "--password PASSWORD", - String, - "Password for basic, digest and x509 authentication") do |password| - options.auth.password = password - options.auth.user_cert_password = password - end - - opts.on("-c", - "--ca-path PATH", - String, - "Path to CA certificates directory, defaults to #{options.auth.ca_path.inspect}") do |ca_path| - raise ArgumentError, "Path specified in --ca-path is not a directory!" unless File.directory? ca_path - raise ArgumentError, "Path specified in --ca-path is not readable!" unless File.readable? ca_path - - options.auth.ca_path = ca_path - end - - opts.on("-f", - "--ca-file PATH", - String, - "Path to CA certificates in a file") do |ca_file| - raise ArgumentError, "File specified in --ca-file is not a file!" unless File.file? ca_file - raise ArgumentError, "File specified in --ca-file is not readable!" unless File.readable? ca_file - - options.auth.ca_file = ca_file - end - - opts.on("-x", - "--user-cred FILE", - String, - "Path to user's x509 credentials, defaults to #{options.auth.user_cert.inspect}") do |user_cred| - raise ArgumentError, "File specified in --user-cred is not a file!" unless File.file? user_cred - raise ArgumentError, "File specified in --user-cred is not readable!" unless File.readable? user_cred - - options.auth.user_cert = user_cred - end - - opts.on("-X", - "--voms", - "Using VOMS credentials; modifies behavior of the X509 authN module") do |voms| - - options.auth.voms = true - end - - opts.on("-y", - "--media-type MEDIA_TYPE", - MEDIA_TYPES, - "Media type for client <-> server communication, only: [#{MEDIA_TYPES.join('|')}], " \ - "defaults to #{options.media_type.inspect}") do |media_type| - options.media_type = media_type - end - - opts.on("-a", - "--compute-title TITLE", - String, - "Value for the occi.core.title attribute, mandatory") do |compute_title| - options.compute_title = compute_title - end - - opts.on("-M", - "--os_tpl IDENTIFIER", - String, - "Identifier of an os_tpl mixin, formatted as SCHEME#TERM or SHORT_SCHEME#TERM or TERM") do |os_tpl| - options.os_tpl = os_tpl.include?('#') ? os_tpl : "os_tpl##{os_tpl}" - end - - opts.on("-R", - "--resource_tpl IDENTIFIER", - String, - "Identifier of an resource_tpl mixin, formatted as SCHEME#TERM or SHORT_SCHEME#TERM or TERM") do |resource_tpl| - options.resource_tpl = resource_tpl.include?('#') ? resource_tpl : "resource_tpl##{resource_tpl}" - end - - opts.on("-F", - "--checks-only", - "Run specified checks against the server model and exit without instantiating a compute") do |checks_only| - options.checks_only = checks_only - end - - opts.on("-G", - "--check-context-mixins", - "Check server's model for contextualization extensions") do |check_context_mixins| - options.check_context_mixins = check_context_mixins - end - - opts.on("-H", - "--check-basic-kinds", - "Check server's model for basic kind definitions") do |check_basic_kinds| - options.check_basic_kinds = check_basic_kinds - end - - opts.on("-I", - "--check-basic-mixins", - "Check server's model for mixin definitions") do |check_basic_mixins| - options.check_basic_mixins = check_basic_mixins - end - - opts.on_tail("-d", - "--debug", - "Enable debugging messages") do |debug| - options.debug = debug - options.log.level = Occi::Probe::Log::DEBUG - end - - opts.on_tail("-h", - "--help", - "Show this message") do - puts opts - exit! true - end - - opts.on_tail("-v", - "--version", - "Show version") do - puts "Probe: #{Occi::Probe::VERSION}" - puts "API: #{Occi::Api::VERSION}" - puts "Core: #{Occi::VERSION}" - exit! true - end -end - -begin - opts.parse!(ARGV) -rescue => ex - puts "UNKNOWN - #{ex.message.capitalize}" - exit Occi::Probe::RetVals::UNKNOWN -end - -## check options for required args (if applicable) -if options.checks_only - unless options.check_basic_kinds || options.check_basic_mixins || options.check_context_mixins - puts "UNKNOWN - Option checks-only requires additional check-* arguments!" - exit Occi::Probe::RetVals::UNKNOWN - end -else - if options.os_tpl.blank? || options.resource_tpl.blank? - puts "UNKNOWN - Required options os_tpl and resource_tpl are missing!" - exit Occi::Probe::RetVals::UNKNOWN - end - - if options.compute_title.blank? - puts "UNKNOWN - Required option compute-title is missing!" - exit Occi::Probe::RetVals::UNKNOWN - end -end - -## initialize logger -logger = Occi::Probe::Log.new(options.log[:out]) -logger.level = options.log[:level] -options.log[:logger] = logger.api_log - -Occi::Probe::Log.debug "Parsed options: #{options.inspect}" - -## establish a connection -begin - Occi::Probe::Log.debug "Establishing a connection to #{options.endpoint}" - - options.auto_connect = true - connect :http, options - - ## run feature checks and exit (if applicable) - if options.check_basic_kinds - Occi::Probe::BASIC_KINDS.each do |kind| - unless model.get_by_id(Occi::Infrastructure.const_get(kind.classify).type_identifier) - puts "CRITICAL - #{kind.upcase} kind is not advertised by the endpoint" - exit Occi::Probe::RetVals::CRITICAL - end - end - end - - if options.check_basic_mixins - Occi::Probe::BASIC_MIXINS.each do |mixin| - unless model.get_by_id(Occi::Infrastructure.const_get(mixin.classify).mixin.type_identifier) - puts "CRITICAL - #{mixin.upcase} mixin is not advertised by the endpoint" - exit Occi::Probe::RetVals::CRITICAL - end - end - end - - if options.check_context_mixins - Occi::Probe::CONTEXTUALIZATION_MIXINS.each do |mixin| - unless model.get_by_id(mixin) - if options.checks_only - puts "WARNING - #{mixin.split('#').last.upcase} contextualization mixin is not advertised by the endpoint" - exit Occi::Probe::RetVals::WARNING - else - puts "CRITICAL - #{mixin.split('#').last.upcase} contextualization mixin is not advertised by the endpoint" - exit Occi::Probe::RetVals::CRITICAL - end - end - end - end - - if options.checks_only - # we got here, so everything is fine - puts "OK - OCCI model contains required kinds, mixins or other extensions" - exit Occi::Probe::RetVals::OK - end - - ## do some clean-up first - ## this is done twice on purpose to trigger force-destroy in rOCCI-server - delete('compute') - delete('compute') - - ## start executing - res = resource(options.resource) - res.title = res.hostname = options.compute_title - - ## add mixins - %w(os_tpl resource_tpl).each do |mixin_idf| - orig_mxn = model.get_by_id(options.send(mixin_idf.to_sym)) - if orig_mxn.blank? - mixin_parts = options.send(mixin_idf.to_sym).split('#') - orig_mxn = mixin(mixin_parts.last, mixin_parts.first, true) - raise ArgumentError, - "The specified mixin is not declared in " \ - "the model! #{options.send(mixin_idf.to_sym).inspect}" if orig_mxn.blank? - end - - res.mixins << orig_mxn - end - - Occi::Probe::Log.debug "Creating #{options.resource.inspect}: #{res.inspect}" - res_link = create res - - state = 'inactive' - Timeout::timeout(options.timeout, Occi::Probe::Errors::Timeout) { - while state != 'active' do - Occi::Probe::Log.debug "Waiting for state \"active\" on " \ - "#{res_link.inspect}, current state #{state.inspect}!" - - res = describe(res_link) - state = res.first.state - - raise Occi::Probe::Errors::ComputeErrored if state == 'error' - - sleep 1 - end - - delete(res_link) - } -rescue Occi::Api::Client::Errors::AuthnError - # authentication failed - puts "CRITICAL - authentication with #{options.endpoint.inspect} failed!" - exit Occi::Probe::RetVals::CRITICAL -rescue Occi::Probe::Errors::ComputeErrored - # deployment explicitly failed - - ## an attempt to do clean-up - unless res_link.blank? - begin - delete(res_link) - rescue => e - # ignore errors - end - end - - puts "CRITICAL - #{options.endpoint.inspect} failed to deploy a COMPUTE instance!" - exit Occi::Probe::RetVals::CRITICAL -rescue Occi::Probe::Errors::Timeout - # the remote server failed to instantiate a compute - # instance in the given timeframe - - ## an attempt to do clean-up - unless res_link.blank? - begin - delete(res_link) - rescue => e - # ignore errors - end - end - - puts "WARNING - #{options.endpoint.inspect} failed to instantiate " \ - "a COMPUTE instance in the given timeframe! Timeout: #{options.timeout}s" - exit Occi::Probe::RetVals::WARNING -rescue Errno::ECONNREFUSED - # the remote server has refused our connection attempt(s) - # there is nothing we can do ... - puts "CRITICAL - Connection refused by #{options.endpoint.inspect}!" - exit Occi::Probe::RetVals::CRITICAL -rescue Errno::ETIMEDOUT, Timeout::Error, Net::OpenTimeout, Net::ReadTimeout - # connection attempt timed out - puts "CRITICAL - Connection to #{options.endpoint.inspect} timed out!" - exit Occi::Probe::RetVals::CRITICAL -rescue OpenSSL::SSL::SSLError => ssl_ex - # generic SSL error raised whilst establishing a connection - # possibly an untrusted server cert or invalid user credentials - raise ssl_ex if options.debug - puts "CRITICAL - SSL connection with #{options.endpoint.inspect} could " \ - "not be established! #{ssl_ex.message}" - exit Occi::Probe::RetVals::CRITICAL -rescue OpenSSL::PKey::RSAError => key_ex - # generic X.509 error raised whilst reading user's credentials from a file - # possibly a wrong password or mangled/unsupported credential format - raise key_ex if options.debug - puts "UNKNOWN - Failed to acquire local user credentials! #{key_ex.message}" - exit Occi::Probe::RetVals::UNKNOWN -rescue StandardError => ex - # something went wrong during the execution - # hide the stack trace in non-debug modes - - ## an attempt to do clean-up - unless res_link.blank? - begin - delete(res_link) - rescue => e - # ignore errors - end - end - - raise ex if options.debug - - if ex.message && ex.message.include?('HTTP Response status') - puts "CRITICAL - Unexpected response from #{options.endpoint.inspect}! #{ex.message}" - exit Occi::Probe::RetVals::CRITICAL - else - puts "UNKNOWN - An error occurred! #{ex.message}" - exit Occi::Probe::RetVals::UNKNOWN - end -end - -puts "OK - COMPUTE instance successfully created & cleaned up. Ref. #{res_link.inspect}" From 4f94710dea0dbc425c87116f1fd26edb29520845 Mon Sep 17 00:00:00 2001 From: eimamagi Date: Tue, 22 Nov 2016 10:58:12 +0100 Subject: [PATCH 12/15] Corrected release number. --- nagios-plugins-fedcloud.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index 6e35673..d9a3a39 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -3,7 +3,7 @@ Summary: Nagios plugins for EGI FedCloud services Name: nagios-plugins-fedcloud Version: 0.1.1 -Release: 7%{?dist} +Release: 1%{?dist} License: ASL 2.0 Group: Network/Monitoring Source0: %{name}-%{version}.tar.gz From d43b122c7cc35114d5b90b4c400e4f072486176e Mon Sep 17 00:00:00 2001 From: eimamagi Date: Wed, 30 Nov 2016 16:23:53 +0100 Subject: [PATCH 13/15] Added legacy OCCI probe. --- nagios-plugins-fedcloud.spec | 2 +- src/check_occi_compute_create | 467 ++++++++++++++++++++++++++++++++++ 2 files changed, 468 insertions(+), 1 deletion(-) create mode 100755 src/check_occi_compute_create diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index d9a3a39..600c9e3 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -2,7 +2,7 @@ Summary: Nagios plugins for EGI FedCloud services Name: nagios-plugins-fedcloud -Version: 0.1.1 +Version: 0.1.2 Release: 1%{?dist} License: ASL 2.0 Group: Network/Monitoring diff --git a/src/check_occi_compute_create b/src/check_occi_compute_create new file mode 100755 index 0000000..6dd69f7 --- /dev/null +++ b/src/check_occi_compute_create @@ -0,0 +1,467 @@ +#!/opt/occi-cli/embedded/bin/ruby + +# -------------------------------------------------------------------------- # +# 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. # +#--------------------------------------------------------------------------- # + +require 'rubygems' +require 'occi-api' +require 'optparse' +require 'uri' +require 'logger' +require 'openssl' +require 'timeout' + +extend Occi::Api::Dsl + +## aux class definitions +module Occi::Probe + VERSION = "0.0.3" + BASIC_KINDS = %w(compute network storage) + BASIC_MIXINS = %w(os_tpl resource_tpl) + CONTEXTUALIZATION_MIXINS = %w(http://schemas.openstack.org/instance/credentials#public_key http://schemas.openstack.org/compute/instance#user_data) +end + +module Occi::Probe::RetVals + OK = 0 + WARNING = 1 + CRITICAL = 2 + UNKNOWN = 3 +end + +module Occi::Probe::Errors + class Timeout < Timeout::Error; end + class ComputeErrored < RuntimeError; end +end + +module Occi::Probe + class Log < ::Occi::Log + SUBSCRIPTION_HANDLE = "rOCCI-probe.log" + + attr_reader :api_log + + def initialize(log_dev, log_prefix = '[rOCCI-probe]') + @api_log = ::Occi::Api::Log.new(log_dev) + super + end + + def close + super + @api_log.close + end + + # @param severity [::Logger::Severity] severity + def level=(severity) + @api_log.level = severity + super + end + + def core_log + @api_log.core_log + end + end +end + +## constants +AUTH_METHODS = [:x509, :basic, :digest, :none].freeze +MEDIA_TYPES = ["application/occi+json", "text/plain,text/occi", "text/plain", "text/occi"].freeze + +options = Hashie::Mash.new + +## defaults +options.debug = false + +options.log = {} +options.log.out = STDERR +options.log.level = Occi::Probe::Log::ERROR + +options.endpoint = "http://localhost:3000" +options.timeout = 180 + +options.auth = {} +options.auth.type = "none" +options.auth.user_cert = "#{ENV['HOME']}/.globus/usercred.pem" +options.auth.ca_path = "/etc/grid-security/certificates" +options.auth.username = "anonymous" +options.auth.ca_file = nil +options.auth.voms = false + +options.output_format = :plain + +options.attributes = [] +options.media_type = "text/plain,text/occi" + +options.resource = 'compute' +options.action = 'create' + +## parse arguments +opts = OptionParser.new do |opts| + opts.banner = %{Usage: check_occi_compute_create [OPTIONS]} + + opts.separator "" + opts.separator "Options:" + + opts.on("-e", + "--endpoint URI", + String, + "OCCI server URI, defaults to #{options.endpoint.inspect}") do |endpoint| + options.endpoint = URI(endpoint).to_s + end + + opts.on("-n", + "--auth METHOD", + AUTH_METHODS, + "Authentication method, only: [#{AUTH_METHODS.join('|')}], defaults " \ + "to #{options.auth.type.inspect}") do |auth| + options.auth.type = auth.to_s + end + + opts.on("-t", + "--timeout SEC", + Integer, + "Default timeout for all HTTP connections, in seconds") do |timeout| + raise "Timeout has to be a number larger than 0!" if timeout < 1 + options.timeout = timeout + end + + opts.on("-u", + "--username USER", + String, + "Username for basic or digest authentication, defaults to " \ + "#{options.auth.username.inspect}") do |username| + options.auth.username = username + end + + opts.on("-p", + "--password PASSWORD", + String, + "Password for basic, digest and x509 authentication") do |password| + options.auth.password = password + options.auth.user_cert_password = password + end + + opts.on("-c", + "--ca-path PATH", + String, + "Path to CA certificates directory, defaults to #{options.auth.ca_path.inspect}") do |ca_path| + raise ArgumentError, "Path specified in --ca-path is not a directory!" unless File.directory? ca_path + raise ArgumentError, "Path specified in --ca-path is not readable!" unless File.readable? ca_path + + options.auth.ca_path = ca_path + end + + opts.on("-f", + "--ca-file PATH", + String, + "Path to CA certificates in a file") do |ca_file| + raise ArgumentError, "File specified in --ca-file is not a file!" unless File.file? ca_file + raise ArgumentError, "File specified in --ca-file is not readable!" unless File.readable? ca_file + + options.auth.ca_file = ca_file + end + + opts.on("-x", + "--user-cred FILE", + String, + "Path to user's x509 credentials, defaults to #{options.auth.user_cert.inspect}") do |user_cred| + raise ArgumentError, "File specified in --user-cred is not a file!" unless File.file? user_cred + raise ArgumentError, "File specified in --user-cred is not readable!" unless File.readable? user_cred + + options.auth.user_cert = user_cred + end + + opts.on("-X", + "--voms", + "Using VOMS credentials; modifies behavior of the X509 authN module") do |voms| + + options.auth.voms = true + end + + opts.on("-y", + "--media-type MEDIA_TYPE", + MEDIA_TYPES, + "Media type for client <-> server communication, only: [#{MEDIA_TYPES.join('|')}], " \ + "defaults to #{options.media_type.inspect}") do |media_type| + options.media_type = media_type + end + + opts.on("-a", + "--compute-title TITLE", + String, + "Value for the occi.core.title attribute, mandatory") do |compute_title| + options.compute_title = compute_title + end + + opts.on("-M", + "--os_tpl IDENTIFIER", + String, + "Identifier of an os_tpl mixin, formatted as SCHEME#TERM or SHORT_SCHEME#TERM or TERM") do |os_tpl| + options.os_tpl = os_tpl.include?('#') ? os_tpl : "os_tpl##{os_tpl}" + end + + opts.on("-R", + "--resource_tpl IDENTIFIER", + String, + "Identifier of an resource_tpl mixin, formatted as SCHEME#TERM or SHORT_SCHEME#TERM or TERM") do |resource_tpl| + options.resource_tpl = resource_tpl.include?('#') ? resource_tpl : "resource_tpl##{resource_tpl}" + end + + opts.on("-F", + "--checks-only", + "Run specified checks against the server model and exit without instantiating a compute") do |checks_only| + options.checks_only = checks_only + end + + opts.on("-G", + "--check-context-mixins", + "Check server's model for contextualization extensions") do |check_context_mixins| + options.check_context_mixins = check_context_mixins + end + + opts.on("-H", + "--check-basic-kinds", + "Check server's model for basic kind definitions") do |check_basic_kinds| + options.check_basic_kinds = check_basic_kinds + end + + opts.on("-I", + "--check-basic-mixins", + "Check server's model for mixin definitions") do |check_basic_mixins| + options.check_basic_mixins = check_basic_mixins + end + + opts.on_tail("-d", + "--debug", + "Enable debugging messages") do |debug| + options.debug = debug + options.log.level = Occi::Probe::Log::DEBUG + end + + opts.on_tail("-h", + "--help", + "Show this message") do + puts opts + exit! true + end + + opts.on_tail("-v", + "--version", + "Show version") do + puts "Probe: #{Occi::Probe::VERSION}" + puts "API: #{Occi::Api::VERSION}" + puts "Core: #{Occi::VERSION}" + exit! true + end +end + +begin + opts.parse!(ARGV) +rescue => ex + puts "UNKNOWN - #{ex.message.capitalize}" + exit Occi::Probe::RetVals::UNKNOWN +end + +## check options for required args (if applicable) +if options.checks_only + unless options.check_basic_kinds || options.check_basic_mixins || options.check_context_mixins + puts "UNKNOWN - Option checks-only requires additional check-* arguments!" + exit Occi::Probe::RetVals::UNKNOWN + end +else + if options.os_tpl.blank? || options.resource_tpl.blank? + puts "UNKNOWN - Required options os_tpl and resource_tpl are missing!" + exit Occi::Probe::RetVals::UNKNOWN + end + + if options.compute_title.blank? + puts "UNKNOWN - Required option compute-title is missing!" + exit Occi::Probe::RetVals::UNKNOWN + end +end + +## initialize logger +logger = Occi::Probe::Log.new(options.log[:out]) +logger.level = options.log[:level] +options.log[:logger] = logger.api_log + +Occi::Probe::Log.debug "Parsed options: #{options.inspect}" + +## establish a connection +begin + Occi::Probe::Log.debug "Establishing a connection to #{options.endpoint}" + + options.auto_connect = true + connect :http, options + + ## run feature checks and exit (if applicable) + if options.check_basic_kinds + Occi::Probe::BASIC_KINDS.each do |kind| + unless model.get_by_id(Occi::Infrastructure.const_get(kind.classify).type_identifier) + puts "CRITICAL - #{kind.upcase} kind is not advertised by the endpoint" + exit Occi::Probe::RetVals::CRITICAL + end + end + end + + if options.check_basic_mixins + Occi::Probe::BASIC_MIXINS.each do |mixin| + unless model.get_by_id(Occi::Infrastructure.const_get(mixin.classify).mixin.type_identifier) + puts "CRITICAL - #{mixin.upcase} mixin is not advertised by the endpoint" + exit Occi::Probe::RetVals::CRITICAL + end + end + end + + if options.check_context_mixins + Occi::Probe::CONTEXTUALIZATION_MIXINS.each do |mixin| + unless model.get_by_id(mixin) + if options.checks_only + puts "WARNING - #{mixin.split('#').last.upcase} contextualization mixin is not advertised by the endpoint" + exit Occi::Probe::RetVals::WARNING + else + puts "CRITICAL - #{mixin.split('#').last.upcase} contextualization mixin is not advertised by the endpoint" + exit Occi::Probe::RetVals::CRITICAL + end + end + end + end + + if options.checks_only + # we got here, so everything is fine + puts "OK - OCCI model contains required kinds, mixins or other extensions" + exit Occi::Probe::RetVals::OK + end + + ## do some clean-up first + ## this is done twice on purpose to trigger force-destroy in rOCCI-server + delete('compute') + delete('compute') + + ## start executing + res = resource(options.resource) + res.title = res.hostname = options.compute_title + + ## add mixins + %w(os_tpl resource_tpl).each do |mixin_idf| + orig_mxn = model.get_by_id(options.send(mixin_idf.to_sym)) + if orig_mxn.blank? + mixin_parts = options.send(mixin_idf.to_sym).split('#') + orig_mxn = mixin(mixin_parts.last, mixin_parts.first, true) + raise ArgumentError, + "The specified mixin is not declared in " \ + "the model! #{options.send(mixin_idf.to_sym).inspect}" if orig_mxn.blank? + end + + res.mixins << orig_mxn + end + + Occi::Probe::Log.debug "Creating #{options.resource.inspect}: #{res.inspect}" + res_link = create res + + state = 'inactive' + Timeout::timeout(options.timeout, Occi::Probe::Errors::Timeout) { + while state != 'active' do + Occi::Probe::Log.debug "Waiting for state \"active\" on " \ + "#{res_link.inspect}, current state #{state.inspect}!" + + res = describe(res_link) + state = res.first.state + + raise Occi::Probe::Errors::ComputeErrored if state == 'error' + + sleep 1 + end + + delete(res_link) + } +rescue Occi::Api::Client::Errors::AuthnError + # authentication failed + puts "CRITICAL - authentication with #{options.endpoint.inspect} failed!" + exit Occi::Probe::RetVals::CRITICAL +rescue Occi::Probe::Errors::ComputeErrored + # deployment explicitly failed + + ## an attempt to do clean-up + unless res_link.blank? + begin + delete(res_link) + rescue => e + # ignore errors + end + end + + puts "CRITICAL - #{options.endpoint.inspect} failed to deploy a COMPUTE instance!" + exit Occi::Probe::RetVals::CRITICAL +rescue Occi::Probe::Errors::Timeout + # the remote server failed to instantiate a compute + # instance in the given timeframe + + ## an attempt to do clean-up + unless res_link.blank? + begin + delete(res_link) + rescue => e + # ignore errors + end + end + + puts "WARNING - #{options.endpoint.inspect} failed to instantiate " \ + "a COMPUTE instance in the given timeframe! Timeout: #{options.timeout}s" + exit Occi::Probe::RetVals::WARNING +rescue Errno::ECONNREFUSED + # the remote server has refused our connection attempt(s) + # there is nothing we can do ... + puts "CRITICAL - Connection refused by #{options.endpoint.inspect}!" + exit Occi::Probe::RetVals::CRITICAL +rescue Errno::ETIMEDOUT, Timeout::Error, Net::OpenTimeout, Net::ReadTimeout + # connection attempt timed out + puts "CRITICAL - Connection to #{options.endpoint.inspect} timed out!" + exit Occi::Probe::RetVals::CRITICAL +rescue OpenSSL::SSL::SSLError => ssl_ex + # generic SSL error raised whilst establishing a connection + # possibly an untrusted server cert or invalid user credentials + raise ssl_ex if options.debug + puts "CRITICAL - SSL connection with #{options.endpoint.inspect} could " \ + "not be established! #{ssl_ex.message}" + exit Occi::Probe::RetVals::CRITICAL +rescue OpenSSL::PKey::RSAError => key_ex + # generic X.509 error raised whilst reading user's credentials from a file + # possibly a wrong password or mangled/unsupported credential format + raise key_ex if options.debug + puts "UNKNOWN - Failed to acquire local user credentials! #{key_ex.message}" + exit Occi::Probe::RetVals::UNKNOWN +rescue StandardError => ex + # something went wrong during the execution + # hide the stack trace in non-debug modes + + ## an attempt to do clean-up + unless res_link.blank? + begin + delete(res_link) + rescue => e + # ignore errors + end + end + + raise ex if options.debug + + if ex.message && ex.message.include?('HTTP Response status') + puts "CRITICAL - Unexpected response from #{options.endpoint.inspect}! #{ex.message}" + exit Occi::Probe::RetVals::CRITICAL + else + puts "UNKNOWN - An error occurred! #{ex.message}" + exit Occi::Probe::RetVals::UNKNOWN + end +end + +puts "OK - COMPUTE instance successfully created & cleaned up. Ref. #{res_link.inspect}" From f503766c271a89c1cc434f92c36d4c2b0d1ec1a1 Mon Sep 17 00:00:00 2001 From: Daniel Vrcic Date: Fri, 9 Dec 2016 19:23:14 +0100 Subject: [PATCH 14/15] refactored keystone token and cert check code --- MANIFEST.in | 5 + Makefile | 12 +- README.md | 1 + nagios-plugins-fedcloud.spec | 10 +- pymodule/__init__.py | 0 pymodule/helpers.py | 170 ++++++++++++ setup.py | 38 +++ src/cdmiprobe.py | 207 +++----------- src/novaprobe.py | 515 +++++++++++++---------------------- 9 files changed, 453 insertions(+), 505 deletions(-) create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 pymodule/__init__.py create mode 100644 pymodule/helpers.py create mode 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d7ff25d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include src/* +include pymodule/* +include nagios-plugins-fedcloud.spec + +recursive-exclude pymodule *.pyc diff --git a/Makefile b/Makefile index 0936b88..62df448 100644 --- a/Makefile +++ b/Makefile @@ -6,13 +6,19 @@ PKGVERSION=$(shell grep -s '^Version:' $(SPECFILE) | sed -e 's/Version: *//') dist: rm -rf dist - mkdir -p dist/${PKGNAME}-${PKGVERSION} - cp -pr ${FILES} dist/${PKGNAME}-${PKGVERSION}/. - cd dist ; tar cfz ../${PKGNAME}-${PKGVERSION}.tar.gz ${PKGNAME}-${PKGVERSION} + python setup.py sdist + mv dist/${PKGNAME}-${PKGVERSION}.tar.gz . rm -rf dist +srpm: dist + rpmbuild -ts --define='dist .el6' ${PKGNAME}-${PKGVERSION}.tar.gz + +rpm: dist + rpmbuild -ta ${PKGNAME}-${PKGVERSION}.tar.gz + sources: dist clean: rm -rf ${PKGNAME}-${PKGVERSION}.tar.gz + rm -f MANIFEST rm -rf dist diff --git a/README.md b/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ + diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index 600c9e3..69e95d0 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -1,8 +1,10 @@ +# sitelib +%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %define dir /usr/libexec/argo-monitoring/probes/fedcloud Summary: Nagios plugins for EGI FedCloud services Name: nagios-plugins-fedcloud -Version: 0.1.2 +Version: 0.1.3 Release: 1%{?dist} License: ASL 2.0 Group: Network/Monitoring @@ -19,18 +21,22 @@ Requires: pyOpenSSL %setup -q %build +%{__python} setup.py build %install rm -rf $RPM_BUILD_ROOT +%{__python} setup.py install --skip-build --root %{buildroot} --record=INSTALLED_FILES install --directory ${RPM_BUILD_ROOT}%{dir} install --mode 755 src/* ${RPM_BUILD_ROOT}%{dir} +install -d -m 755 %{buildroot}/%{python_sitelib}/nagios_plugins_fedcloud %clean rm -rf $RPM_BUILD_ROOT -%files +%files -f INSTALLED_FILES %defattr(-,root,root,-) %{dir} +%{python_sitelib}/nagios_plugins_fedcloud %changelog * Tue Nov 22 2016 Emir Imamagic - 0.1.1-7%{?dist} diff --git a/pymodule/__init__.py b/pymodule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pymodule/helpers.py b/pymodule/helpers.py new file mode 100644 index 0000000..5995b7b --- /dev/null +++ b/pymodule/helpers.py @@ -0,0 +1,170 @@ +import sys +import re +import socket +import requests +import json +from time import sleep + +from OpenSSL.SSL import TLSv1_METHOD, Context, Connection +from OpenSSL.SSL import VERIFY_PEER +from OpenSSL.SSL import Error as SSLError +from OpenSSL.SSL import WantReadError as SSLWantReadError +from urlparse import urlparse + +strerr = '' +num_excp_expand = 0 + +def nagios_out(status, msg, retcode): + sys.stdout.write(status+": "+msg+"\n") + sys.exit(retcode) + +def get_keystone_token(host, userca, capath, timeout): + if verify_cert(host, capath, timeout): + o = urlparse(host) + if o.scheme != 'https': + nagios_out('Critical', 'Connection error %s - Probe expects HTTPS endpoint' % (o.scheme+'://'+o.netloc), 2) + try: + # fetch unscoped token + token_suffix = '' + if o.netloc.endswith('v2.0'): + token_suffix = token_suffix+'/tokens' + elif o.netloc.endswith('5000'): + token_suffix = token_suffix+'/v2.0/tokens' + + headers, payload, token = {}, {}, None + headers.update({'Accept': '*/*'}) + + headers = {'content-type': 'application/json', 'accept': 'application/json'} + payload = {'auth': {'voms': True}} + response = requests.post(o.scheme+'://'+o.netloc+token_suffix, headers=headers, + data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) + response.raise_for_status() + token = response.json()['access']['token']['id'] + except(KeyError, IndexError) as e: + nagios_out('Critical', 'Could not fetch unscoped keystone token from response: Key not found %s' % errmsg_from_excp(e), 2) + except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: + nagios_out('Critical', 'Connection error %s - %s' % (o.netloc+token_suffix, errmsg_from_excp(e)), 2) + + try: + # use unscoped token to get a list of allowed tenants mapped to + # ops VO from VOMS proxy cert + tenant_suffix= '' + if o.netloc.endswith("v2.0"): + tenant_suffix = tenant_suffix+'/tenants' + else: + tenant_suffix = tenant_suffix+'/v2.0/tenants' + headers = {'content-type': 'application/json', 'accept': 'application/json'} + headers.update({'x-auth-token': token}) + response = requests.get(o.scheme+'://'+o.netloc+tenant_suffix, headers=headers, + data=None, cert=userca, verify=False, timeout=timeout) + response.raise_for_status() + tenants = response.json()['tenants'] + tenant = '' + for t in tenants: + if 'ops' in t['name']: + tenant = t['name'] + except(KeyError, IndexError) as e: + nagios_out('Critical', 'Could not fetch allowed tenants from response: Key not found %s' % errmsg_from_excp(e), 2) + except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: + nagios_out('Critical', 'Connection error %s - %s' % (o.scheme+'://'+o.netloc+tenant_suffix, errmsg_from_excp(e)), 2) + + try: + # get scoped token for allowed tenant + headers = {'content-type': 'application/json', 'accept': 'application/json'} + payload = {'auth': {'voms': True, 'tenantName': tenant}} + response = requests.post(o.scheme+'://'+o.netloc+token_suffix, headers=headers, + data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) + response.raise_for_status() + token = response.json()['access']['token']['id'] + except(KeyError, IndexError) as e: + nagios_out('Critical', 'Could not fetch scoped keystone token for %s from response: Key not found %s' % (tenant, errmsg_from_excp(e)), 2) + except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: + nagios_out('Critical', 'Connection error %s - %s' % (o.scheme+'://'+o.netloc+token_suffix, errmsg_from_excp(e)), 2) + + return token, tenant, response + +def errmsg_from_excp(e, level=5): + global strerr, num_excp_expand + if isinstance(e, Exception) and getattr(e, 'args', False): + num_excp_expand += 1 + if not errmsg_from_excp(e.args): + return strerr + elif isinstance(e, dict): + for s in e.iteritems(): + errmsg_from_excp(s) + elif isinstance(e, list): + for s in e: + errmsg_from_excp(s) + elif isinstance(e, tuple): + for s in e: + errmsg_from_excp(s) + elif isinstance(e, str): + if num_excp_expand <= level: + strerr += e + ' ' + +def verify_cert(host, capath, timeout, cncheck=True): + server_ctx = Context(TLSv1_METHOD) + server_cert_chain = [] + server_ctx.load_verify_locations(None, capath) + + host = re.split("/*", host)[1] + if ':' in host: + host = host.split(':') + server = host[0] + port = int(host[1] if not '?' in host[1] else host[1].split('?')[0]) + else: + server = host + port = 443 + + def verify_cb(conn, cert, errnum, depth, ok): + server_cert_chain.append(cert) + return ok + server_ctx.set_verify(VERIFY_PEER, verify_cb) + + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setblocking(1) + sock.settimeout(timeout) + sock.connect((server, port)) + except (socket.error, socket.timeout) as e: + nagios_out('Critical', 'Connection error %s - %s' % (server + ':' + str(port), + errmsg_from_excp(e)), + 2) + + server_conn = Connection(server_ctx, sock) + server_conn.set_connect_state() + + def iosock_try(): + ok = True + try: + server_conn.do_handshake() + sleep(0.5) + except SSLWantReadError as e: + ok = False + pass + except Exception as e: + raise e + return ok + + try: + while True: + if iosock_try(): + break + + if cncheck: + server_subject = server_cert_chain[-1].get_subject() + if server != server_subject.CN: + nagios_out('Critical', 'Server certificate CN %s does not match %s' % (server_subject.CN, server), 2) + + except SSLError as e: + if 'sslv3 alert handshake failure' in errmsg_from_excp(e): + pass + else: + nagios_out('Critical', 'Connection error %s - %s' % (server + ':' + str(port), + errmsg_from_excp(e, level=1)), + 2) + finally: + server_conn.shutdown() + server_conn.close() + + return True diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a8e2527 --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +from distutils.core import setup +import glob, sys + +NAME='nagios-plugins-fedcloud' +DESTDIR='/usr/libexec/argo-monitoring/probes/fedcloud' + +def get_ver(): + try: + for line in open(NAME+'.spec'): + if "Version:" in line: + return line.split()[1] + except IOError: + print "Make sure that %s is in directory" % (NAME+'.spec') + sys.exit(1) + + +setup(name=NAME, + version=get_ver(), + license='ASL 2.0', + author='SRCE', + author_email='dvrcic@srce.hr, eimamagi@srce.hr', + description='Package include probes for EGI FedCloud services', + platforms='noarch', + long_description=''' + This package includes probes for EGI FedCloud services. + Currently it supports the following tests: + - AppDB workflow + - CDMI + - Openstack Nova + - FedCloud Accounting Freshness + - OCCI compute create + - Perun + ''', + url='https://github.com/ARGOeu/nagios-plugins-fedcloud', + data_files=[(DESTDIR, glob.glob('src/*'))], + packages=['nagios_plugins_fedcloud'], + package_dir={'nagios_plugins_fedcloud': 'pymodule/'}, + ) diff --git a/src/cdmiprobe.py b/src/cdmiprobe.py index c8fd08c..6653d89 100755 --- a/src/cdmiprobe.py +++ b/src/cdmiprobe.py @@ -15,12 +15,10 @@ # limitations under the License. -import argparse, re, random, signal -import requests, sys, os, json, socket, time +import argparse, re, random +import requests, sys, os, json, time -from OpenSSL.SSL import TLSv1_METHOD, Context, Connection -from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT -from OpenSSL.SSL import Error as SSLError +from nagios_plugins_fedcloud import helpers HEADER_CDMI_VERSIONS = [{'X-CDMI-Specification-Version': '1.0.2'}, {'X-CDMI-Specification-Version': '1.0.1'}] CDMI_CONTAINER = 'application/cdmi-container' @@ -35,92 +33,17 @@ DEFAULT_PORT = 443 -def errmsg_from_excp(e): - if getattr(e, 'args', False): - retstr = '' - if isinstance(e.args, basestring): - return e.args - elif isinstance(e.args, list) or isinstance(e.args, tuple) \ - or isinstance(e.args, dict): - for s in e.args: - if isinstance(s, str): - retstr += s + ' ' - if isinstance(s, tuple): - retstr += ' '.join(s) - if isinstance(s, Exception): - retstr = str(s) - return retstr - else: - for s in e.args: - retstr += str(s) + ' ' - return retstr - else: - return str(e) - - -def server_ok(serverarg, capath, timeout): - server_ctx = Context(TLSv1_METHOD) - server_ctx.load_verify_locations(None, capath) - - def verify_cb(conn, cert, errnum, depth, ok): - return ok - server_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) - - serverarg = re.split("/*", serverarg)[1] - if ':' in serverarg: - serverarg = serverarg.split(':') - server = serverarg[0] - port = int(serverarg[1]) - else: - server = serverarg - port = DEFAULT_PORT - - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((server, port)) - - server_conn = Connection(server_ctx, sock) - server_conn.set_connect_state() - - try: - def handler(signum, frame): - raise socket.error([('Timeout', 'after', str(timeout) + 's')]) - - signal.signal(signal.SIGALRM, handler) - signal.alarm(timeout) - server_conn.do_handshake() - signal.alarm(0) - except socket.timeout as e: - nagios_out('Critical', 'Connection error %s - %s' % (server + ':' + str(port), errmsg_from_excp(e)), 2) - - server_conn.shutdown() - server_conn.close() - - except(SSLError, socket.error) as e: - if 'sslv3 alert handshake failure' in errmsg_from_excp(e): - pass - else: - nagios_out('Critical', 'Connection error %s - %s' % (server + ':' + str(port), errmsg_from_excp(e)), 2) - - return True - - -def nagios_out(status, msg, retcode): - sys.stdout.write(status+": "+msg+"\n") - sys.exit(retcode) - - -def get_token(server, userca, capath, timeout, cdmiver): +def keystone_url(server, userca, capath, timeout, cdmiver): try: # initiate unauthorized response (HTTP 401) with keystone URL - headers, token = {}, None + headers = {} headers.update(cdmiver) headers.update({'Accept': '*/*'}) response = requests.get(server, headers=headers, cert=userca, verify=False, timeout=timeout) if response.status_code == 400: response = requests.get(server, headers={}, cert=userca, verify=False) except requests.exceptions.ConnectionError as e: - nagios_out('Critical', 'Connection error %s - %s' % (server, errmsg_from_excp(e)), 2) + helpers.nagios_out('Critical', 'Connection error %s - %s' % (server, helpers.errmsg_from_excp(e)), 2) try: # extract public keystone URL from response @@ -128,78 +51,14 @@ def get_token(server, userca, capath, timeout, cdmiver): if ':5000' not in keystone_server: raise AttributeError except(KeyError, IndexError, AttributeError): - raise Exception('Could not fetch keystone server from response: Key not found %s' % errmsg_from_excp(e)) + raise Exception('Could not fetch keystone server from response: Key not found %s' % helpers.errmsg_from_excp(e)) - if server_ok(keystone_server, capath, timeout): - try: - # fetch unscoped token - token_suffix = '' - if keystone_server.endswith("v2.0"): - token_suffix = token_suffix+'/tokens' - else: - token_suffix = token_suffix+'/v2.0/tokens' - - headers, payload, token = {}, {}, None - headers.update(cdmiver) - headers.update({'Accept': '*/*'}) - - headers = {'content-type': 'application/json', 'accept': 'application/json'} - payload = {'auth': {'voms': True}} - response = requests.post(keystone_server+token_suffix, headers=headers, - data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) - response.raise_for_status() - token = response.json()['access']['token']['id'] - except(KeyError, IndexError) as e: - raise Exception('Could not fetch unscoped keystone token from response: Key not found %s' % errmsg_from_excp(e)) - except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'Connection error %s - %s' % (keystone_server+token_suffix, errmsg_from_excp(e)), 2) - - try: - # use unscoped token to get a list of allowed tenants mapped to - # ops VO from VOMS proxy cert - tenant_suffix= '' - if keystone_server.endswith("v2.0"): - tenant_suffix = tenant_suffix+'/tenants' - else: - tenant_suffix = tenant_suffix+'/v2.0/tenants' - headers = {'content-type': 'application/json', 'accept': 'application/json'} - headers.update({'x-auth-token': token}) - response = requests.get(keystone_server+tenant_suffix, headers=headers, - data=None, cert=userca, verify=False, timeout=timeout) - response.raise_for_status() - tenants = response.json()['tenants'] - tenant = '' - for t in tenants: - if 'ops' in t['name']: - tenant = t['name'] - break - else: - # if there is no "ops" tenant, use the first one - tenant = tenants[0]['name'] - except(KeyError, IndexError) as e: - raise Exception('could not fetch allowed tenants from response: Key not found %s' % errmsg_from_excp(e)) - except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'connection error %s - %s' % (keystone_server+tenant_suffix, errmsg_from_excp(e)), 2) - - try: - # get scoped token for allowed tenant - headers = {'content-type': 'application/json', 'accept': 'application/json'} - payload = {'auth': {'voms': True, 'tenantName': tenant}} - response = requests.post(keystone_server+token_suffix, headers=headers, - data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) - response.raise_for_status() - token = response.json()['access']['token']['id'] - except(KeyError, IndexError) as e: - raise Exception('Critical', 'could not fetch scoped keystone token for %s from response: Key not found %s' % (tenant, errmsg_from_excp(e))) - except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'connection error %s - %s' % (keystone_server+token_suffix, errmsg_from_excp(e)), 2) - - return token + return keystone_server def create_container(argholder, ks_token, cdmiver, container_name): # create container - headers, payload= {}, {} + headers = {} headers.update(cdmiver) headers.update({'accept': CDMI_CONTAINER, 'content-type': CDMI_CONTAINER}) @@ -211,7 +70,7 @@ def create_container(argholder, ks_token, cdmiver, container_name): def delete_container(argholder, ks_token, cdmiver, container_name): # remove container - headers, payload= {}, {} + headers = {} headers.update(cdmiver) headers.update({'x-auth-token': ks_token}) response = requests.delete(argholder.endpoint + container_name + '/', @@ -238,7 +97,7 @@ def create_dataobject(argholder, ks_token, cdmiver, container_name, obj_name, def get_dataobject(argholder, ks_token, cdmiver, container_name, obj_name): # get data object - headers, payload= {}, {} + headers = {} headers.update(cdmiver) headers.update({'accept': CDMI_OBJECT, 'content-type': CDMI_OBJECT}) @@ -251,7 +110,7 @@ def get_dataobject(argholder, ks_token, cdmiver, container_name, obj_name): def delete_dataobject(argholder, ks_token, cdmiver, container_name, obj_name): # remove data object - headers, payload= {}, {} + headers = {} headers.update(cdmiver) headers.update({'x-auth-token': ks_token}) response = requests.delete(argholder.endpoint + container_name + obj_name, @@ -265,11 +124,11 @@ def clean_up(argholder, ks_token, cdmiver, container_name, obj_name=None): delete_dataobject(argholder, ks_token, cdmiver, container_name, obj_name) except requests.exceptions.HTTPError as e: - sys.stderr.write('Clean up error: %s\n' % errmsg_from_excp(e)) + sys.stderr.write('Clean up error: %s\n' % helpers.errmsg_from_excp(e)) try: delete_container(argholder, ks_token, cdmiver, container_name) except requests.exceptions.HTTPError as e: - sys.stderr.write('Clean up error: %s\n' % errmsg_from_excp(e)) + sys.stderr.write('Clean up error: %s\n' % helpers.errmsg_from_excp(e)) def main(): @@ -294,27 +153,31 @@ class ArgHolder(object): msg_error_args = '' for arg in argnotspec: msg_error_args += '%s ' % (arg) - nagios_out('Unknown', 'command-line arguments not specified, '+msg_error_args, 3) + helpers.nagios_out('Unknown', 'command-line arguments not specified, '+msg_error_args, 3) else: if not argholder.endpoint.startswith("http") \ or not os.path.isfile(argholder.cert) \ or not type(argholder.timeout) == int \ or not os.path.isdir(argholder.capath): - nagios_out('Unknown', 'command-line arguments are not correct', 3) + helpers.nagios_out('Unknown', 'command-line arguments are not correct', 3) - if server_ok(argholder.endpoint, argholder.capath, argholder.timeout): + if helpers.verify_cert(argholder.endpoint, argholder.capath, argholder.timeout, cncheck=False): ver = None for v, cdmiver in enumerate(HEADER_CDMI_VERSIONS): # fetch scoped token for ops VO try: - ks_token = get_token(argholder.endpoint, - argholder.cert, - argholder.capath, - argholder.timeout, - cdmiver) + keystone_server = keystone_url(argholder.endpoint, + argholder.cert, + argholder.capath, + argholder.timeout, cdmiver) + ks_token = helpers.get_keystone_token(keystone_server, + argholder.cert, + argholder.capath, + argholder.timeout)[0] + except Exception as e: if v == len(HEADER_CDMI_VERSIONS) - 1: - nagios_out('Critical', e.message, 2) + helpers.nagios_out('Critical', e.message, 2) # if we successfully fetched token, then we also have # supported CDMI Specification version @@ -328,14 +191,14 @@ class ArgHolder(object): try: create_container(argholder, ks_token, ver, container_name) except requests.exceptions.HTTPError as e: - nagios_out('Critical', 'test - create_container failed %s' % errmsg_from_excp(e), 2) + helpers.nagios_out('Critical', 'test - create_container failed %s' % helpers.errmsg_from_excp(e), 2) try: create_dataobject(argholder, ks_token, ver, container_name, obj_name, randdata) except requests.exceptions.HTTPError as e: clean_up(argholder, ks_token, ver, container_name) - nagios_out('Critical', 'test - create_dataobject failed %s' % errmsg_from_excp(e), 2) + helpers.nagios_out('Critical', 'test - create_dataobject failed %s' % helpers.errmsg_from_excp(e), 2) time.sleep(OPWAIT) try: @@ -345,7 +208,7 @@ class ArgHolder(object): raise requests.exceptions.HTTPError('data integrity violated') except requests.exceptions.HTTPError as e: clean_up(argholder, ks_token, ver, container_name, obj_name) - nagios_out('Critical', 'test - get_dataobject failed %s' % errmsg_from_excp(e), 2) + helpers.nagios_out('Critical', 'test - get_dataobject failed %s' % helpers.errmsg_from_excp(e), 2) time.sleep(OPWAIT) newranddata = ''.join(random.sample('abcdefghij1234567890', 20)) @@ -355,7 +218,7 @@ class ArgHolder(object): obj_name, newranddata) except requests.exceptions.HTTPError as e: clean_up(argholder, ks_token, ver, container_name, obj_name) - nagios_out('Critical', 'test - update_dataobject failed %s' % errmsg_from_excp(e), 2) + helpers.nagios_out('Critical', 'test - update_dataobject failed %s' % helpers.errmsg_from_excp(e), 2) time.sleep(OPWAIT) try: @@ -365,7 +228,7 @@ class ArgHolder(object): raise requests.exceptions.HTTPError('data integrity violated') except requests.exceptions.HTTPError as e: clean_up(argholder, ks_token, ver, container_name, obj_name) - nagios_out('Critical', 'test - get_dataobject failed %s' % errmsg_from_excp(e), 2) + helpers.nagios_out('Critical', 'test - get_dataobject failed %s' % helpers.errmsg_from_excp(e), 2) time.sleep(OPWAIT) try: @@ -373,15 +236,15 @@ class ArgHolder(object): obj_name) except requests.exceptions.HTTPError as e: clean_up(argholder, ks_token, ver, container_name, obj_name) - nagios_out('Critical', 'test - delete_dataobject failed %s' % errmsg_from_excp(e), 2) + helpers.nagios_out('Critical', 'test - delete_dataobject failed %s' % helpers.errmsg_from_excp(e), 2) time.sleep(OPWAIT) try: delete_container(argholder, ks_token, ver, container_name) except requests.exceptions.HTTPError as e: clean_up(argholder, ks_token, ver, container_name, obj_name) - nagios_out('Critical', 'test - delete_container failed %s' % errmsg_from_excp(e), 2) + helpers.nagios_out('Critical', 'test - delete_container failed %s' % helpers.errmsg_from_excp(e), 2) - nagios_out('OK', 'container and dataobject creating, fetching and removing tests were successful', 0) + helpers.nagios_out('OK', 'container and dataobject creating, fetching and removing tests were successful', 0) main() diff --git a/src/novaprobe.py b/src/novaprobe.py index 2607fa4..d6b86b3 100755 --- a/src/novaprobe.py +++ b/src/novaprobe.py @@ -16,178 +16,40 @@ #from pprint import pprint -import argparse, re, random, signal -import requests, sys, os, json, socket -from urlparse import urlparse +import argparse, re +import requests, sys, os, json import time -from OpenSSL.SSL import TLSv1_METHOD -from OpenSSL.SSL import Context, Connection -from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT -from OpenSSL.SSL import Error as SSLError +from nagios_plugins_fedcloud import helpers DEFAULT_PORT = 443 TIMEOUT_CREATE_DELETE = 600 SERVER_NAME = 'cloudmonprobe-servertest' -def errmsg_from_excp(e): - if getattr(e, 'args', False): - retstr = '' - if isinstance(e.args, list) or isinstance(e.args, tuple) \ - or isinstance(e.args, dict): - for s in e.args: - if isinstance(s, str): - retstr += s + ' ' - if isinstance(s, tuple) or isinstance(s, tuple): - retstr += ' '.join(s) - return retstr - elif isinstance(e.args, str): - return e.args - else: - for s in e.args: - retstr += str(s) + ' ' - return retstr - else: - return str(e) - - -def server_ok(serverarg, capath, timeout): - server_ctx = Context(TLSv1_METHOD) - server_ctx.load_verify_locations(None, capath) - - def verify_cb(conn, cert, errnum, depth, ok): - return ok - server_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) - - serverarg = re.split("/*", serverarg)[1] - if ':' in serverarg: - serverarg = serverarg.split(':') - server = serverarg[0] - port = int(serverarg[1] if not '?' in serverarg[1] else serverarg[1].split('?')[0]) - else: - server = serverarg - port = DEFAULT_PORT +strerr = '' +num_excp_expand = 0 +def get_info(tenant, last_response): try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((server, port)) - - server_conn = Connection(server_ctx, sock) - server_conn.set_connect_state() - try: - def handler(signum, frame): - raise socket.error([('Timeout', 'after', str(timeout) + 's')]) - - signal.signal(signal.SIGALRM, handler) - signal.alarm(timeout) - server_conn.do_handshake() - signal.alarm(0) - except socket.timeout as e: - nagios_out('Critical', 'Connection error %s - %s' % (server + ':' + str(port), - errmsg_from_excp(e)), - 2) - server_conn.shutdown() - server_conn.close() - - except(SSLError, socket.error) as e: - if 'sslv3 alert handshake failure' in errmsg_from_excp(e): - pass - else: - nagios_out('Critical', 'Connection error %s - %s' % (server + ':' + str(port), - errmsg_from_excp(e)), - 2) - - return True - - -def nagios_out(status, msg, retcode): - sys.stdout.write(status+": "+msg+"\n") - sys.exit(retcode) - - -def get_info(server, userca, capath, timeout): - if server_ok(server, capath, timeout): - o = urlparse(server) - if o.scheme != 'https': - nagios_out('Critical', 'Connection error %s - Probe expects HTTPS endpoint' % (o.scheme+'://'+o.netloc), 2) - try: - # fetch unscoped token - token_suffix = '' - if o.netloc.endswith("v2.0"): - token_suffix = token_suffix+'/tokens' - else: - token_suffix = token_suffix+'/v2.0/tokens' - - headers, payload, token = {}, {}, None - headers.update({'Accept': '*/*'}) - - headers = {'content-type': 'application/json', 'accept': 'application/json'} - payload = {'auth': {'voms': True}} - response = requests.post(o.scheme+'://'+o.netloc+token_suffix, headers=headers, - data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) - response.raise_for_status() - token = response.json()['access']['token']['id'] - except(KeyError, IndexError) as e: - nagios_out('Critical', 'Could not fetch unscoped keystone token from response: Key not found %s' % errmsg_from_excp(e), 2) - except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'Connection error %s - %s' % (o.netloc+token_suffix, errmsg_from_excp(e)), 2) - - try: - # use unscoped token to get a list of allowed tenants mapped to - # ops VO from VOMS proxy cert - tenant_suffix= '' - if o.netloc.endswith("v2.0"): - tenant_suffix = tenant_suffix+'/tenants' - else: - tenant_suffix = tenant_suffix+'/v2.0/tenants' - headers = {'content-type': 'application/json', 'accept': 'application/json'} - headers.update({'x-auth-token': token}) - response = requests.get(o.scheme+'://'+o.netloc+tenant_suffix, headers=headers, - data=None, cert=userca, verify=False, timeout=timeout) - response.raise_for_status() - tenants = response.json()['tenants'] - tenant = '' - for t in tenants: - if 'ops' in t['name']: - tenant = t['name'] - except(KeyError, IndexError) as e: - nagios_out('Critical', 'Could not fetch allowed tenants from response: Key not found %s' % errmsg_from_excp(e), 2) - except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'Connection error %s - %s' % (o.scheme+'://'+o.netloc+tenant_suffix, errmsg_from_excp(e)), 2) + tenant_id = last_response.json()['access']['token']['tenant']['id'] + except(KeyError, IndexError) as e: + helpers.nagios_out('Critical', 'Could not fetch id for tenant %s: Key not found %s' % (tenant, helpers.errmsg_from_excp(e)), 2) - try: - # get scoped token for allowed tenant - headers = {'content-type': 'application/json', 'accept': 'application/json'} - payload = {'auth': {'voms': True, 'tenantName': tenant}} - response = requests.post(o.scheme+'://'+o.netloc+token_suffix, headers=headers, - data=json.dumps(payload), cert=userca, verify=False, timeout=timeout) - response.raise_for_status() - token = response.json()['access']['token']['id'] - except(KeyError, IndexError) as e: - nagios_out('Critical', 'Could not fetch scoped keystone token for %s from response: Key not found %s' % (tenant, errmsg_from_excp(e)), 2) - except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'Connection error %s - %s' % (o.scheme+'://'+o.netloc+token_suffix, errmsg_from_excp(e)), 2) - - try: - tenant_id = response.json()['access']['token']['tenant']['id'] - except(KeyError, IndexError) as e: - nagios_out('Critical', 'Could not fetch id for tenant %s: Key not found %s' % (tenant, errmsg_from_excp(e)), 2) - - try: - service_catalog = response.json()['access']['serviceCatalog'] - except(KeyError, IndexError) as e: - nagios_out('Critical', 'Could not fetch service catalog: Key not found %s' % (errmsg_from_excp(e))) + try: + service_catalog = last_response.json()['access']['serviceCatalog'] + except(KeyError, IndexError) as e: + helpers.nagios_out('Critical', 'Could not fetch service catalog: Key not found %s' % (helpers.errmsg_from_excp(e))) - try: - nova_url = None - for e in service_catalog: - if e['type'] == 'compute': - nova_url = e['endpoints'][0]['publicURL'] - assert nova_url is not None - except(KeyError, IndexError, AssertionError) as e: - nagios_out('Critical', 'Could not fetch nova compute service URL: Key not found %s' % (errmsg_from_excp(e))) + try: + nova_url = None + for e in service_catalog: + if e['type'] == 'compute': + nova_url = e['endpoints'][0]['publicURL'] + assert nova_url is not None + except(KeyError, IndexError, AssertionError) as e: + helpers.nagios_out('Critical', 'Could not fetch nova compute service URL: Key not found %s' % (helpers.errmsg_from_excp(e))) - return token, tenant_id, nova_url + return tenant_id, nova_url def main(): @@ -215,92 +77,152 @@ class ArgHolder(object): msg_error_args = '' for arg in argnotspec: msg_error_args += '%s ' % (arg) - nagios_out('Unknown', 'command-line arguments not specified, '+msg_error_args, 3) + helpers.nagios_out('Unknown', 'command-line arguments not specified, '+msg_error_args, 3) else: if not argholder.endpoint.startswith("http") \ or not os.path.isfile(argholder.cert) \ or not type(argholder.timeout) == int \ or not os.path.isdir(argholder.capath): - nagios_out('Unknown', 'command-line arguments are not correct', 3) + helpers.nagios_out('Unknown', 'command-line arguments are not correct', 3) - if server_ok(argholder.endpoint, argholder.capath, argholder.timeout): - ks_token, tenant_id, nova_url = get_info(argholder.endpoint, - argholder.cert, - argholder.capath, - argholder.timeout) + ks_token, tenant, last_response = helpers.get_keystone_token(argholder.endpoint, argholder.cert, argholder.capath, argholder.timeout) + tenant_id, nova_url = get_info(tenant, last_response) - # remove once endpoints properly expose images openstackish way - if not argholder.image: - try: - image = re.search("(\?image=)([\w\-]*)", argholder.endpoint).group(2) - except (AttributeError, IndexError): - nagios_out('Unknown', 'image UUID is not specifed for endpoint', 3) - else: - image = argholder.image + # remove once endpoints properly expose images openstackish way + if not argholder.image: + try: + image = re.search("(\?image=)([\w\-]*)", argholder.endpoint).group(2) + except (AttributeError, IndexError): + helpers.nagios_out('Unknown', 'image UUID is not specifed for endpoint', 3) + else: + image = argholder.image - if not argholder.flavor: - try: - flavor = re.search("(\&flavor=)([\w\.\-]*)", argholder.endpoint).group(2) - except (AttributeError, IndexError): - nagios_out('Unknown', 'flavor is not specified for image %s' % (image), 3) - else: - flavor = argholder.flavor + if not argholder.flavor: + try: + flavor = re.search("(\&flavor=)([\w\.\-]*)", argholder.endpoint).group(2) + except (AttributeError, IndexError): + helpers.nagios_out('Unknown', 'flavor is not specified for image %s' % (image), 3) + else: + flavor = argholder.flavor + + if argholder.verb: + print 'Endpoint:%s' % (argholder.endpoint) + print 'Image:%s' % (image) + print 'Flavor:%s' % (flavor) + print 'Auth token (cut to 64 chars): %.64s' % ks_token + print 'Tenant OPS, ID:%s' % tenant_id + print 'Nova: %s' % nova_url + # fetch flavor_id for given flavor (resource) + try: + headers, payload= {}, {} + headers.update({'x-auth-token': ks_token}) + response = requests.get(nova_url + '/flavors', headers=headers, cert=argholder.cert, + verify=False, timeout=argholder.timeout) + response.raise_for_status() + + flavors = response.json()['flavors'] + flavor_id = None + for f in flavors: + if f['name'] == flavor: + flavor_id = f['id'] + assert flavor_id is not None + if argholder.verb: + print "Flavor %s, ID:%s" % (flavor, flavor_id) + except (requests.exceptions.ConnectionError, + requests.exceptions.Timeout, requests.exceptions.HTTPError) as e: + helpers.nagios_out('Critical', 'could not fetch flavor ID, endpoint does not correctly exposes available flavors: %s' % helpers.errmsg_from_excp(e), 2) + except (AssertionError, IndexError, AttributeError) as e: + helpers.nagios_out('Critical', 'could not fetch flavor ID, endpoint does not correctly exposes available flavors: %s' % str(e), 2) + + # create server + try: + headers, payload= {}, {} + headers = {'content-type': 'application/json', 'accept': 'application/json'} + headers.update({'x-auth-token': ks_token}) + payload = {'server': {'name': SERVER_NAME, + 'imageRef': nova_url + '/images/%s' % (image), + 'flavorRef': nova_url + '/flavors/%s'% (flavor_id)}} + response = requests.post(nova_url + '/servers', headers=headers, + data=json.dumps(payload), + cert=argholder.cert, verify=False, + timeout=argholder.timeout) + response.raise_for_status() + server_id = response.json()['server']['id'] if argholder.verb: - print 'Endpoint:%s' % (argholder.endpoint) - print 'Image:%s' % (image) - print 'Flavor:%s' % (flavor) - print 'Auth token (cut to 64 chars): %.64s' % ks_token - print 'Tenant OPS, ID:%s' % tenant_id - print 'Nova: %s' % nova_url - - # fetch flavor_id for given flavor (resource) + print "Creating server:%s name:%s" % (server_id, SERVER_NAME) + except (requests.exceptions.ConnectionError, + requests.exceptions.Timeout, requests.exceptions.HTTPError, + AssertionError, IndexError, AttributeError) as e: + helpers.nagios_out('Critical', 'Could not launch server from image UUID:%s: %s' % (image, helpers.errmsg_from_excp(e)), 2) + + + i, s, e, sleepsec, tss = 0, 0, 0, 1, 3 + server_createt, server_deletet= 0, 0 + server_built = False + st = time.time() + if argholder.verb: + sys.stdout.write('Check server status every %ds: ' % (sleepsec)) + while i < TIMEOUT_CREATE_DELETE/sleepsec: + # server status try: headers, payload= {}, {} headers.update({'x-auth-token': ks_token}) - response = requests.get(nova_url + '/flavors', headers=headers, cert=argholder.cert, - verify=False, timeout=argholder.timeout) + response = requests.get(nova_url + '/servers/%s' % (server_id), + headers=headers, cert=argholder.cert, + verify=False, + timeout=argholder.timeout) response.raise_for_status() - - flavors = response.json()['flavors'] - flavor_id = None - for f in flavors: - if f['name'] == flavor: - flavor_id = f['id'] - assert flavor_id is not None + status = response.json()['server']['status'] if argholder.verb: - print "Flavor %s, ID:%s" % (flavor, flavor_id) + sys.stdout.write(status+' ') + sys.stdout.flush() + if 'ACTIVE' in status: + server_built = True + et = time.time() + break + time.sleep(sleepsec) except (requests.exceptions.ConnectionError, - requests.exceptions.Timeout, requests.exceptions.HTTPError) as e: - nagios_out('Critical', 'could not fetch flavor ID, endpoint does not correctly exposes available flavors: %s' % errmsg_from_excp(e), 2) - except (AssertionError, IndexError, AttributeError) as e: - nagios_out('Critical', 'could not fetch flavor ID, endpoint does not correctly exposes available flavors: %s' % str(e), 2) + requests.exceptions.Timeout, requests.exceptions.HTTPError, + AssertionError, IndexError, AttributeError) as e: + if i < tss and argholder.verb: + sys.stdout.write('\n') + sys.stdout.write('Try to fetch server:%s status one more time. Error was %s\n' % (server_id, + helpers.errmsg_from_excp(e))) + sys.stdout.write('Check server status every %ds: ' % (sleepsec)) + else: + helpers.nagios_out('Critical', 'could not fetch server:%s status: %s' % (server_id, helpers.errmsg_from_excp(e)), 2) + i += 1 + else: + if argholder.verb: + sys.stdout.write('\n') + helpers.nagios_out('Critical', 'could not create server:%s, timeout:%d exceeded' % (server_id, TIMEOUT_CREATE_DELETE), 2) + + server_createt = round(et - st, 2) - # create server + if server_built: + if argholder.verb: + print "\nServer created in %.2f seconds" % (server_createt) + + # server delete try: headers, payload= {}, {} - headers = {'content-type': 'application/json', 'accept': 'application/json'} headers.update({'x-auth-token': ks_token}) - payload = {'server': {'name': SERVER_NAME, - 'imageRef': nova_url + '/images/%s' % (image), - 'flavorRef': nova_url + '/flavors/%s'% (flavor_id)}} - response = requests.post(nova_url + '/servers', headers=headers, - data=json.dumps(payload), - cert=argholder.cert, verify=False, - timeout=argholder.timeout) - response.raise_for_status() - server_id = response.json()['server']['id'] + response = requests.delete(nova_url + '/servers/%s' % + (server_id), headers=headers, + cert=argholder.cert, verify=False, + timeout=argholder.timeout) if argholder.verb: - print "Creating server:%s name:%s" % (server_id, SERVER_NAME) + print "Trying to delete server=%s" % server_id + response.raise_for_status() except (requests.exceptions.ConnectionError, requests.exceptions.Timeout, requests.exceptions.HTTPError, AssertionError, IndexError, AttributeError) as e: - nagios_out('Critical', 'Could not launch server from image UUID:%s: %s' % (image, errmsg_from_excp(e)), 2) - + helpers.nagios_out('Critical', 'could not execute DELETE server=%s: %s' % (server_id, helpers.errmsg_from_excp(e)), 2) - i, s, e, sleepsec, tss = 0, 0, 0, 1, 3 - server_createt, server_deletet= 0, 0 - server_built = False + # waiting for DELETED status + i = 0 + server_deleted = False st = time.time() if argholder.verb: sys.stdout.write('Check server status every %ds: ' % (sleepsec)) @@ -309,124 +231,61 @@ class ArgHolder(object): try: headers, payload= {}, {} headers.update({'x-auth-token': ks_token}) - response = requests.get(nova_url + '/servers/%s' % (server_id), - headers=headers, cert=argholder.cert, - verify=False, + + response = requests.get(nova_url + '/servers', headers=headers, + cert=argholder.cert, verify=False, timeout=argholder.timeout) - response.raise_for_status() - status = response.json()['server']['status'] - if argholder.verb: - sys.stdout.write(status+' ') - sys.stdout.flush() - if 'ACTIVE' in status: - server_built = True + servfound = False + for s in response.json()['servers']: + if server_id == s['id']: + servfound = True + response = requests.get(nova_url + '/servers/%s' % + (server_id), headers=headers, + cert=argholder.cert, verify=False, + timeout=argholder.timeout) + response.raise_for_status() + status = response.json()['server']['status'] + if argholder.verb: + sys.stdout.write(status+' ') + sys.stdout.flush() + if status.startswith('DELETED'): + server_deleted = True + et = time.time() + break + + if not servfound: + server_deleted = True et = time.time() + if argholder.verb: + sys.stdout.write('DELETED') + sys.stdout.flush() break + time.sleep(sleepsec) except (requests.exceptions.ConnectionError, - requests.exceptions.Timeout, requests.exceptions.HTTPError, - AssertionError, IndexError, AttributeError) as e: - if i < tss and argholder.verb: + requests.exceptions.Timeout, + requests.exceptions.HTTPError, AssertionError, + IndexError, AttributeError) as e: + + server_deleted = True + et = time.time() + + if argholder.verb: sys.stdout.write('\n') - sys.stdout.write('Try to fetch server:%s status one more time. Error was %s\n' % (server_id, - errmsg_from_excp(e))) - sys.stdout.write('Check server status every %ds: ' % (sleepsec)) - else: - nagios_out('Critical', 'could not fetch server:%s status: %s' % (server_id, errmsg_from_excp(e)), 2) + sys.stdout.write('Could not fetch server:%s status: %s - server is DELETED' % (server_id, + helpers.errmsg_from_excp(e))) + break i += 1 else: if argholder.verb: sys.stdout.write('\n') - nagios_out('Critical', 'could not create server:%s, timeout:%d exceeded' % (server_id, TIMEOUT_CREATE_DELETE), 2) + helpers.nagios_out('Critical', 'could not delete server:%s, timeout:%d exceeded' % (server_id, TIMEOUT_CREATE_DELETE), 2) - server_createt = round(et - st, 2) + server_deletet = round(et - st, 2) - if server_built: - if argholder.verb: - print "\nServer created in %.2f seconds" % (server_createt) - - # server delete - try: - headers, payload= {}, {} - headers.update({'x-auth-token': ks_token}) - response = requests.delete(nova_url + '/servers/%s' % - (server_id), headers=headers, - cert=argholder.cert, verify=False, - timeout=argholder.timeout) - if argholder.verb: - print "Trying to delete server=%s" % server_id - response.raise_for_status() - except (requests.exceptions.ConnectionError, - requests.exceptions.Timeout, requests.exceptions.HTTPError, - AssertionError, IndexError, AttributeError) as e: - nagios_out('Critical', 'could not execute DELETE server=%s: %s' % (server_id, errmsg_from_excp(e)), 2) - - # waiting for DELETED status - i = 0 - server_deleted = False - st = time.time() - if argholder.verb: - sys.stdout.write('Check server status every %ds: ' % (sleepsec)) - while i < TIMEOUT_CREATE_DELETE/sleepsec: - # server status - try: - headers, payload= {}, {} - headers.update({'x-auth-token': ks_token}) - - response = requests.get(nova_url + '/servers', headers=headers, - cert=argholder.cert, verify=False, - timeout=argholder.timeout) - servfound = False - for s in response.json()['servers']: - if server_id == s['id']: - servfound = True - response = requests.get(nova_url + '/servers/%s' % - (server_id), headers=headers, - cert=argholder.cert, verify=False, - timeout=argholder.timeout) - response.raise_for_status() - status = response.json()['server']['status'] - if argholder.verb: - sys.stdout.write(status+' ') - sys.stdout.flush() - if status.startswith('DELETED'): - server_deleted = True - et = time.time() - break - - if not servfound: - server_deleted = True - et = time.time() - if argholder.verb: - sys.stdout.write('DELETED') - sys.stdout.flush() - break - - time.sleep(sleepsec) - except (requests.exceptions.ConnectionError, - requests.exceptions.Timeout, - requests.exceptions.HTTPError, AssertionError, - IndexError, AttributeError) as e: - - server_deleted = True - et = time.time() - - if argholder.verb: - sys.stdout.write('\n') - sys.stdout.write('Could not fetch server:%s status: %s - server is DELETED' % (server_id, - errmsg_from_excp(e))) - break - i += 1 - else: - if argholder.verb: - sys.stdout.write('\n') - nagios_out('Critical', 'could not delete server:%s, timeout:%d exceeded' % (server_id, TIMEOUT_CREATE_DELETE), 2) - - server_deletet = round(et - st, 2) - - if server_built and server_deleted: - if argholder.verb: - print "\nServer=%s deleted in %.2f seconds" % (server_id, server_deletet) - nagios_out('OK', 'Compute instance=%s created(%.2fs) and destroyed(%.2fs)' % (server_id, server_createt, server_deletet), 0) + if server_built and server_deleted: + if argholder.verb: + print "\nServer=%s deleted in %.2f seconds" % (server_id, server_deletet) + helpers.nagios_out('OK', 'Compute instance=%s created(%.2fs) and destroyed(%.2fs)' % (server_id, server_createt, server_deletet), 0) main() From 36cf00c069683a0cf82178b529ab5f7a9f259f02 Mon Sep 17 00:00:00 2001 From: Daniel Vrcic Date: Tue, 13 Dec 2016 10:19:32 +0100 Subject: [PATCH 15/15] spec version bumped with refactorings info --- nagios-plugins-fedcloud.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nagios-plugins-fedcloud.spec b/nagios-plugins-fedcloud.spec index 69e95d0..95933ca 100644 --- a/nagios-plugins-fedcloud.spec +++ b/nagios-plugins-fedcloud.spec @@ -39,6 +39,8 @@ rm -rf $RPM_BUILD_ROOT %{python_sitelib}/nagios_plugins_fedcloud %changelog +* Tue Dec 13 2016 Daniel Vrcic - 0.1.3-1%{?dist} +- refactored keystone token and cert check code * Tue Nov 22 2016 Emir Imamagic - 0.1.1-7%{?dist} - Probes location aligned with guidelines * Fri May 13 2016 Daniel Vrcic - 0.1.0-6%{?dist}