diff --git a/monitoring/alarmdefs/views.py b/monitoring/alarmdefs/views.py index e59b1c70..5dcf6cd0 100644 --- a/monitoring/alarmdefs/views.py +++ b/monitoring/alarmdefs/views.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import logging + from django.contrib import messages from django.core.paginator import Paginator, EmptyPage from django.core.urlresolvers import reverse_lazy, reverse # noqa @@ -24,7 +26,7 @@ from horizon import tables from horizon.utils import functions as utils from horizon import workflows -import monascaclient.exc as exc +from monascaclient import exc from monitoring.alarmdefs import constants from monitoring.alarmdefs import forms as alarm_forms from monitoring.alarmdefs import tables as alarm_tables @@ -34,6 +36,7 @@ from monitoring import api from openstack_dashboard import policy +LOG = logging.getLogger(__name__) PREV_PAGE_LIMIT = 100 @@ -53,7 +56,8 @@ class IndexView(tables.DataTableView): results = paginator.page(1) except EmptyPage: results = paginator.page(paginator.num_pages) - except Exception: + except Exception as ex: + LOG.exception(str(ex)) messages.error(self.request, _("Could not retrieve alarm definitions")) return results @@ -147,7 +151,7 @@ class AlarmDetailView(TemplateView): notification['undetermined'] = False notifications.append(notification) # except exceptions.NOT_FOUND: - except exc.HTTPException: + except exc.HttpError: msg = _("Notification %s has already been deleted.") % id notifications.append({"id": id, "name": unicode(msg), @@ -214,7 +218,7 @@ class AlarmEditView(forms.ModalFormView): notification['undetermined'] = False notifications.append(notification) # except exceptions.NOT_FOUND: - except exc.HTTPException: + except exc.HttpError: msg = _("Notification %s has already been deleted.") % id messages.warning(self.request, msg) diff --git a/monitoring/alarms/views.py b/monitoring/alarms/views.py index a1373c8b..c932cfe3 100644 --- a/monitoring/alarms/views.py +++ b/monitoring/alarms/views.py @@ -297,9 +297,9 @@ class AlarmHistoryView(tables.DataTableView): limit = utils.get_page_size(self.request) try: results = api.monitor.alarm_history(self.request, - object_id, - page_offset, - limit) + object_id, + page_offset, + limit) paginator = Paginator(results, limit) contacts = paginator.page(1) except EmptyPage: diff --git a/monitoring/api/client.py b/monitoring/api/client.py new file mode 100644 index 00000000..b9aef1ad --- /dev/null +++ b/monitoring/api/client.py @@ -0,0 +1,99 @@ +# Copyright 2017 Fujitsu LIMITED +# +# 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. + +from horizon import exceptions +from horizon.utils import memoized + +from oslo_log import log as logging + +from openstack_dashboard.api import base + +from monascaclient import client as mon_client +from monitoring.config import local_settings as settings + +LOG = logging.getLogger(__name__) + +INSECURE = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) +CACERT = getattr(settings, 'OPENSTACK_SSL_CACERT', None) + +KEYSTONE_SERVICE = 'identity' +MONITORING_SERVICE = getattr(settings, 'MONITORING_SERVICE_TYPE', 'monitoring') + +VERSIONS = base.APIVersionManager( + MONITORING_SERVICE, + preferred_version=getattr(settings, + 'OPENSTACK_API_VERSIONS', + {}).get(MONITORING_SERVICE, 2.0) +) +VERSIONS.load_supported_version(2.0, {'client': mon_client, 'version': '2_0'}) + + +def _get_endpoint(request): + try: + endpoint = base.url_for(request, + service_type=settings.MONITORING_SERVICE_TYPE, + endpoint_type=settings.MONITORING_ENDPOINT_TYPE) + except exceptions.ServiceCatalogException: + endpoint = 'http://127.0.0.1:8070/v2.0' + LOG.warning('Monasca API location could not be found in Service ' + 'Catalog, using default: {0}'.format(endpoint)) + return endpoint + + +def _get_auth_params_from_request(request): + """Extracts the properties from the request object needed by the monascaclient + call below. These will be used to memoize the calls to monascaclient + """ + LOG.debug('Extracting intel from request') + return ( + request.user.user_domain_id, + request.user.token.id, + request.user.tenant_id, + request.user.token.project.get('domain_id'), + base.url_for(request, MONITORING_SERVICE), + base.url_for(request, KEYSTONE_SERVICE) + ) + + +@memoized.memoized_with_request(_get_auth_params_from_request) +def monascaclient(request_auth_params, version=None): + + ( + user_domain_id, + token_id, + project_id, + project_domain_id, + monasca_url, + auth_url + ) = request_auth_params + + # NOTE(trebskit) this is bit hacky, we should + # go straight into using numbers as version representation + version = (VERSIONS.get_active_version()['version'] + if not version else version) + + LOG.debug('Monasca::Client ' + % (monasca_url, version)) + + c = mon_client.Client(api_version=version, + token=token_id, + project_id=project_id, + user_domain_id=user_domain_id, + project_domain_id=project_domain_id, + insecure=INSECURE, + cert=CACERT, + auth_url=auth_url, + endpoint=monasca_url) + return c diff --git a/monitoring/api/monitor.py b/monitoring/api/monitor.py index cbe18158..373aebf8 100644 --- a/monitoring/api/monitor.py +++ b/monitoring/api/monitor.py @@ -10,59 +10,25 @@ # License for the specific language governing permissions and limitations # under the License. -import logging +from oslo_log import log -from monascaclient import client as monasca_client -from openstack_dashboard.api import base -from monitoring.config import local_settings as settings - - -LOG = logging.getLogger(__name__) - - -def format_parameters(params): - parameters = {} - for count, p in enumerate(params, 1): - parameters['Parameters.member.%d.ParameterKey' % count] = p - parameters['Parameters.member.%d.ParameterValue' % count] = params[p] - return parameters - - -def monasca_endpoint(request): - endpoint = base.url_for(request, settings.MONITORING_SERVICE_TYPE) - if endpoint.endswith('/'): - endpoint = endpoint[:-1] - return endpoint - - -def monascaclient(request, password=None): - api_version = "2_0" - endpoint = monasca_endpoint(request) - LOG.debug('monascaclient connection created using token "%s" , url "%s"' % - (request.user.token.id, endpoint)) - kwargs = { - 'token': request.user.token.id, - 'insecure': settings.OPENSTACK_SSL_NO_VERIFY, - 'ca_file': settings.OPENSTACK_SSL_CACERT, - 'username': request.user.username, - 'password': password - # 'timeout': args.timeout, - # 'ca_file': args.ca_file, - # 'cert_file': args.cert_file, - # 'key_file': args.key_file, - } - client = monasca_client.Client(api_version, endpoint, **kwargs) - client.format_parameters = format_parameters - return client +from openstack_dashboard.contrib.developer.profiler import api as profiler + +from monitoring.api import client + +LOG = log.getLogger(__name__) +@profiler.trace def alarm_list(request, offset=0, limit=10000, marker=None, paginate=False): - result = monascaclient(request).alarms.list(offset=offset, limit=limit) + result = client.monascaclient(request).alarms.list(offset=offset, + limit=limit) return result['elements'] if type(result) is dict else result +@profiler.trace def alarm_list_by_dimension(request, dimensions, offset=0, limit=10000, - marker=None, paginate=False): + marker=None, paginate=False): dim_dict = {} metric = None dimensions = dimensions.split(",") @@ -76,118 +42,152 @@ def alarm_list_by_dimension(request, dimensions, offset=0, limit=10000, else: dim_dict[item] = None if metric: - result = monascaclient(request).alarms.list(offset=offset, limit=limit, - metric_dimensions=dim_dict, - metric_name=metric) + result = client.monascaclient(request).alarms.list(offset=offset, + limit=limit, + metric_dimensions=dim_dict, + metric_name=metric) else: - result = monascaclient(request).alarms.list(offset=offset, limit=limit, - metric_dimensions=dim_dict) + result = client.monascaclient(request).alarms.list(offset=offset, + limit=limit, + metric_dimensions=dim_dict) return result['elements'] if type(result) is dict else result +@profiler.trace def alarm_show(request, alarm_id): - result = monascaclient(request).alarms.get(alarm_id=alarm_id) + result = client.monascaclient(request).alarms.get(alarm_id=alarm_id) return result +@profiler.trace def alarm_delete(request, alarm_id): - return monascaclient(request).alarms.delete(alarm_id=alarm_id) + return client.monascaclient(request).alarms.delete(alarm_id=alarm_id) +@profiler.trace def alarm_history(request, alarm_id, offset=0, limit=10000): - result = monascaclient(request).alarms.history(alarm_id=alarm_id, offset=offset, limit=limit) + result = client.monascaclient(request).alarms.history(alarm_id=alarm_id, + offset=offset, + limit=limit) return result['elements'] if type(result) is dict else result +@profiler.trace def alarm_get(request, alarm_id): - return monascaclient(request).alarms.get(alarm_id=alarm_id) + return client.monascaclient(request).alarms.get(alarm_id=alarm_id) +@profiler.trace def alarm_patch(request, **kwargs): - return monascaclient(request).alarms.patch(**kwargs) + return client.monascaclient(request).alarms.patch(**kwargs) +@profiler.trace def alarmdef_list(request, offset=0, limit=10000, marker=None, paginate=False): - result = monascaclient(request).alarm_definitions.list(offset=offset, limit=limit) + result = client.monascaclient(request).alarm_definitions.list(offset=offset, + limit=limit) return result['elements'] if type(result) is dict else result -def alarmdef_list_by_service(request, service_name, marker=None, paginate=False): +@profiler.trace +def alarmdef_list_by_service(request, service_name, marker=None, + paginate=False): service_dim = {'service': service_name} - result = monascaclient(request).alarm_definitions.list(dimensions=service_dim) + result = client.monascaclient(request).alarm_definitions.list( + dimensions=service_dim) return result['elements'] if type(result) is dict else result +@profiler.trace def alarmdef_delete(request, alarm_id): - return monascaclient(request).alarm_definitions.delete(alarm_id=alarm_id) + return client.monascaclient(request).alarm_definitions.delete( + alarm_id=alarm_id) +@profiler.trace def alarmdef_history(request, alarm_id): - return monascaclient(request).alarm_definitions.history(alarm_id=alarm_id) + return client.monascaclient(request).alarm_definitions.history( + alarm_id=alarm_id) +@profiler.trace def alarmdef_get(request, alarm_id): - return monascaclient(request).alarm_definitions.get(alarm_id=alarm_id) + return client.monascaclient(request).alarm_definitions.get(alarm_id=alarm_id) +@profiler.trace def alarmdef_get_by_name(request, name): - return monascaclient(request).alarm_definitions.list( + return client.monascaclient(request).alarm_definitions.list( name=name, limit=1 ) -def alarmdef_create(request, password=None, **kwargs): - return monascaclient(request, password).alarm_definitions.create(**kwargs) +@profiler.trace +def alarmdef_create(request, **kwargs): + return client.monascaclient(request).alarm_definitions.create(**kwargs) +@profiler.trace def alarmdef_update(request, **kwargs): - return monascaclient(request).alarm_definitions.update(**kwargs) + return client.monascaclient(request).alarm_definitions.update(**kwargs) +@profiler.trace def alarmdef_patch(request, **kwargs): - return monascaclient(request).alarm_definitions.patch(**kwargs) + return client.monascaclient(request).alarm_definitions.patch(**kwargs) -def notification_list(request, offset=0, limit=10000, marker=None, paginate=False): - result = monascaclient(request).notifications.list(offset=offset, limit=limit) +@profiler.trace +def notification_list(request, offset=0, limit=10000, marker=None, + paginate=False): + result = client.monascaclient(request).notifications.list(offset=offset, + limit=limit) return result['elements'] if type(result) is dict else result +@profiler.trace def notification_delete(request, notification_id): - return monascaclient(request).notifications.delete( + return client.monascaclient(request).notifications.delete( notification_id=notification_id) +@profiler.trace def notification_get(request, notification_id): - return monascaclient(request).notifications. \ + return client.monascaclient(request).notifications. \ get(notification_id=notification_id) +@profiler.trace def notification_create(request, **kwargs): - return monascaclient(request).notifications.create(**kwargs) + return client.monascaclient(request).notifications.create(**kwargs) +@profiler.trace def notification_update(request, notification_id, **kwargs): - return monascaclient(request).notifications. \ + return client.monascaclient(request).notifications. \ update(notification_id=notification_id, **kwargs) +@profiler.trace def notification_type_list(request, **kwargs): - result = monascaclient(request).notificationtypes.list(**kwargs) + result = client.monascaclient(request).notificationtypes.list(**kwargs) return result['elements'] if type(result) is dict else result +@profiler.trace def metrics_list(request, **kwargs): - result = monascaclient(request).metrics.list(**kwargs) + result = client.monascaclient(request).metrics.list(**kwargs) return result['elements'] if type(result) is dict else result +@profiler.trace def metrics_measurement_list(request, **kwargs): - result = monascaclient(request).metrics.list_measurements(**kwargs) + result = client.monascaclient(request).metrics.list_measurements(**kwargs) return result['elements'] if type(result) is dict else result +@profiler.trace def metrics_stat_list(request, **kwargs): - result = monascaclient(request).metrics.list_statistics(**kwargs) + result = client.monascaclient(request).metrics.list_statistics(**kwargs) return result['elements'] if type(result) is dict else result diff --git a/monitoring/config/local_settings.py b/monitoring/config/local_settings.py index d0a9ca76..9e50b5bd 100644 --- a/monitoring/config/local_settings.py +++ b/monitoring/config/local_settings.py @@ -41,9 +41,16 @@ MONITORING_SERVICES = getattr( # {'name': _('Instances'), 'groupBy': 'hostname'}]}, # ] +MONITORING_SERVICE_VERSION = getattr( + settings, 'MONITORING_SERVICE_VERSION', '2_0' +) MONITORING_SERVICE_TYPE = getattr( settings, 'MONITORING_SERVICE_TYPE', 'monitoring' ) +MONITORING_ENDPOINT_TYPE = getattr( + # NOTE(trebskit) # will default to OPENSTACK_ENDPOINT_TYPE + settings, 'MONITORING_ENDPOINT_TYPE', None +) # Grafana button titles/file names (global across all projects): GRAFANA_LINKS = [] diff --git a/monitoring/enabled/_50_admin_add_monitoring_panel.py b/monitoring/enabled/_50_admin_add_monitoring_panel.py index 78465467..e9dcc75b 100644 --- a/monitoring/enabled/_50_admin_add_monitoring_panel.py +++ b/monitoring/enabled/_50_admin_add_monitoring_panel.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +from monascaclient import exc + DASHBOARD = "monitoring" # A list of applications to be added to INSTALLED_APPS. @@ -29,10 +31,15 @@ ADD_JS_FILES = ['monitoring/js/app.js', ADD_SCSS_FILES = [ 'monitoring/css/alarm-create.scss'] -from monascaclient import exc # A dictionary of exception classes to be added to HORIZON['exceptions']. +_RECOVERABLE_ERRORS = (exc.UnprocessableEntity, exc.Conflict, + exc.BadRequest, exc.ConnectionError, + exc.Forbidden, exc.InternalServerError) +_NOT_FOUND_ERRORS = (exc.NotFound,) +_UNAUTHORIZED_ERRORS = (exc.Unauthorized,) + ADD_EXCEPTIONS = { - 'recoverable': (exc.HTTPUnProcessable, exc.HTTPConflict, exc.HTTPException), - 'not_found': (exc.HTTPNotFound,), - 'unauthorized': (exc.HTTPUnauthorized,), + 'recoverable': _RECOVERABLE_ERRORS, + 'not_found': _NOT_FOUND_ERRORS, + 'unauthorized': _UNAUTHORIZED_ERRORS, } diff --git a/monitoring/test/test_data/exceptions.py b/monitoring/test/test_data/exceptions.py index dd28f6b6..b8979f4a 100644 --- a/monitoring/test/test_data/exceptions.py +++ b/monitoring/test/test_data/exceptions.py @@ -12,17 +12,14 @@ # under the License. # NOTE(dmllr): Remove me when we require monascaclient >= 1.3.0 -try: - from monascaclient.apiclient import exceptions as monascacli -except ImportError: - from monascaclient.openstack.common.apiclient import exceptions as monascacli +from monascaclient import exc from openstack_dashboard.test.test_data import exceptions def data(TEST): TEST.exceptions = exceptions.data - monitoring_exception = monascacli.ClientException + monitoring_exception = exc.ClientException TEST.exceptions.monitoring = exceptions.create_stubbed_exception( monitoring_exception) diff --git a/tools/tox_install.sh b/tools/tox_install.sh index fe153a82..0a88e89b 100755 --- a/tools/tox_install.sh +++ b/tools/tox_install.sh @@ -9,6 +9,27 @@ BRANCH_NAME=master PACKAGE_NAME=monasca-ui requirements_installed=$(echo "import openstack_requirements" | python 2>/dev/null ; echo $?) +function install_client_depends_on() { + local client_location + if [ -x "$ZUUL_CLONER" ]; then + # install in gate environment + pushd $mydir + $ZUUL_CLONER --cache-dir \ + /opt/git \ + git://git.openstack.org \ + openstack/python-monascaclient + cd openstack/python-monascaclient + echo "Using python-monascaclient $(git log -n 1 --oneline)" + client_location="file://$PWD#egg=python_monascaclient" + popd + else + echo "Using python-monascaclient@master" + client_location="git+https://git.openstack.org/openstack/python-monascaclient@master#egg=python_monascaclient" + fi + edit-constraints $localfile -- "python-monascaclient" "$client_location" + $install_cmd -U "$client_location" +} + set -e git config --global url.https://git.openstack.org/.insteadOf git://git.openstack.org/ @@ -37,8 +58,7 @@ elif [ -x "$ZUUL_CLONER" ]; then --branch $BRANCH_NAME \ git://git.openstack.org \ openstack/requirements - cd openstack/requirements - $install_cmd -e . + cd openstack/requirements ; $install_cmd -e . ; cd - popd else echo "PIP HARDCODE" > /tmp/tox_install.txt @@ -52,6 +72,7 @@ fi # the current repo. It is listed in constraints file and thus any # install will be constrained and we need to unconstrain it. edit-constraints $localfile -- $PACKAGE_NAME "-e file://$PWD#egg=$PACKAGE_NAME" +install_client_depends_on $install_cmd -U $* exit $?