From: sousaedu Date: Thu, 25 Feb 2021 20:32:25 +0000 (+0100) Subject: Adding scraping relation to Mongodb Exporter X-Git-Tag: branch-sol006v331-start~45 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2Fdevops.git;a=commitdiff_plain;h=3884e23c245102cdebe39301786fec2a3f5d5150 Adding scraping relation to Mongodb Exporter Change-Id: Ia96b29a9c7373254cde77ca708d069d3681b1386 Signed-off-by: sousaedu --- diff --git a/installers/charm/mongodb-exporter/.gitignore b/installers/charm/mongodb-exporter/.gitignore new file mode 100644 index 00000000..a4d0de20 --- /dev/null +++ b/installers/charm/mongodb-exporter/.gitignore @@ -0,0 +1,28 @@ +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +venv +.vscode +build +mongodb-exporter.charm +.coverage +.stestr +cover diff --git a/installers/charm/mongodb-exporter/.yamllint.yaml b/installers/charm/mongodb-exporter/.yamllint.yaml new file mode 100644 index 00000000..f300159a --- /dev/null +++ b/installers/charm/mongodb-exporter/.yamllint.yaml @@ -0,0 +1,31 @@ +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +--- +extends: default + +yaml-files: + - "*.yaml" + - "*.yml" + - ".yamllint" +ignore: | + .tox + build/ diff --git a/installers/charm/mongodb-exporter/README.md b/installers/charm/mongodb-exporter/README.md new file mode 100644 index 00000000..84df4c97 --- /dev/null +++ b/installers/charm/mongodb-exporter/README.md @@ -0,0 +1,23 @@ + + +# Prometheus Mongodb Exporter operator Charm for Kubernetes + +## Requirements diff --git a/installers/charm/mongodb-exporter/config.yaml b/installers/charm/mongodb-exporter/config.yaml new file mode 100644 index 00000000..a3aaa21a --- /dev/null +++ b/installers/charm/mongodb-exporter/config.yaml @@ -0,0 +1,39 @@ +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +options: + ingress_whitelist_source_range: + type: string + description: | + A comma-separated list of CIDRs to store in the + ingress.kubernetes.io/whitelist-source-range annotation. + + This can be used to lock down access to + Keystone based on source IP address. + default: "" + tls_secret_name: + type: string + description: TLS Secret name + default: "" + site_url: + type: string + description: Ingress URL + default: "" diff --git a/installers/charm/mongodb-exporter/files/mongodb_exporter_dashboard.json b/installers/charm/mongodb-exporter/files/mongodb_exporter_dashboard.json new file mode 100644 index 00000000..a1b128a0 --- /dev/null +++ b/installers/charm/mongodb-exporter/files/mongodb_exporter_dashboard.json @@ -0,0 +1,840 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "MongoDB Prometheus Exporter Dashboard.", + "editable": true, + "gnetId": 2583, + "graphTooltip": 1, + "id": 9, + "iteration": 1577555358996, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 22, + "panels": [], + "repeat": "env", + "title": "Health", + "type": "row" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "prometheus - Juju generated source", + "decimals": null, + "format": "s", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 10, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "mongodb_instance_uptime_seconds{instance=~\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 1800 + } + ], + "thresholds": "0,360", + "title": "Uptime", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "prometheus - Juju generated source", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 1, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "mongodb_connections{instance=~\"$instance\",state=\"current\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "mongodb_connections", + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Open Connections", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 20, + "panels": [], + "repeat": "env", + "title": "Operations", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus - Juju generated source", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 10, + "x": 0, + "y": 6 + }, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(mongodb_op_counters_total{instance=~\"$instance\"}[$interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{type}}", + "refId": "A", + "step": 240 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Query Operations", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus - Juju generated source", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 8, + "x": 10, + "y": 6 + }, + "id": 9, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "returned", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(mongodb_mongod_metrics_document_total{instance=~\"$instance\"}[$interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{state}}", + "refId": "A", + "step": 240 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Document Operations", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus - Juju generated source", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 6 + }, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(mongodb_mongod_metrics_query_executor_total{instance=~\"$instance\"}[$interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{state}}", + "refId": "A", + "step": 600 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Document Query Executor", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 23, + "panels": [], + "repeat": null, + "title": "Resources", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus - Juju generated source", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 13 + }, + "id": 4, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "mongodb_memory{instance=~\"$instance\",type=~\"resident|virtual\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{type}}", + "refId": "A", + "step": 240 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "decmbytes", + "label": "MB", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus - Juju generated source", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 13 + }, + "id": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(mongodb_network_bytes_total{instance=~\"$instance\"}[$interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{state}}", + "metric": "mongodb_metrics_operation_total", + "refId": "A", + "step": 240 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Network I/O", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 19, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "prometheus - Juju generated source", + "definition": "", + "hide": 0, + "includeAll": true, + "label": "instance", + "multi": true, + "name": "instance", + "options": [], + "query": "label_values(mongodb_connections, instance)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "/.*-(.*?)-.*/", + "tags": [], + "tagsQuery": "label_values(mongodb_connections, instance)", + "type": "query", + "useTags": false + }, + { + "auto": true, + "auto_count": 30, + "auto_min": "10s", + "current": { + "text": "auto", + "value": "$__auto_interval_interval" + }, + "hide": 0, + "label": null, + "name": "interval", + "options": [ + { + "selected": true, + "text": "auto", + "value": "$__auto_interval_interval" + }, + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": false, + "text": "10m", + "value": "10m" + }, + { + "selected": false, + "text": "30m", + "value": "30m" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + } + ], + "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + } + ] + }, + "time": { + "from": "now/d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "MongoDB", + "uid": "HEK4NbtZk", + "version": 2 +} diff --git a/installers/charm/mongodb-exporter/metadata.yaml b/installers/charm/mongodb-exporter/metadata.yaml new file mode 100644 index 00000000..26b6dad4 --- /dev/null +++ b/installers/charm/mongodb-exporter/metadata.yaml @@ -0,0 +1,49 @@ +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +name: mongodb-exporter +summary: OSM Prometheus Mongodb Exporter +description: | + A CAAS charm to deploy OSM's Prometheus Mongodb Exporter. +series: + - kubernetes +tags: + - kubernetes + - osm + - prometheus + - mongodb-exporter +min-juju-version: 2.8.0 +deployment: + type: stateless + service: cluster +resources: + image: + type: oci-image + description: Image of mongodb-exporter + upstream-source: "bitnami/mongodb-exporter:latest" +provides: + prometheus-scrape: + interface: prometheus + grafana-dashboard: + interface: grafana-dashboard +requires: + mongodb: + interface: mongodb diff --git a/installers/charm/mongodb-exporter/requirements.txt b/installers/charm/mongodb-exporter/requirements.txt new file mode 100644 index 00000000..884cf9f9 --- /dev/null +++ b/installers/charm/mongodb-exporter/requirements.txt @@ -0,0 +1,23 @@ +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +ops +git+https://github.com/juju-solutions/resource-oci-image/@c5778285d332edf3d9a538f9d0c06154b7ec1b0b#egg=oci-image diff --git a/installers/charm/mongodb-exporter/src/charm.py b/installers/charm/mongodb-exporter/src/charm.py new file mode 100755 index 00000000..02a600c8 --- /dev/null +++ b/installers/charm/mongodb-exporter/src/charm.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +import logging +from pathlib import Path +from typing import Dict, List, NoReturn +from urllib.parse import urlparse + +from ops.charm import CharmBase +from ops.framework import EventBase, StoredState +from ops.main import main +from ops.model import ActiveStatus, Application, BlockedStatus, MaintenanceStatus, Unit +from oci_image import OCIImageResource, OCIImageResourceError + +from pod_spec import make_pod_spec + +logger = logging.getLogger(__name__) + +MONGODB_EXPORTER_PORT = 9216 + + +class RelationsMissing(Exception): + def __init__(self, missing_relations: List): + self.message = "" + if missing_relations and isinstance(missing_relations, list): + self.message += f'Waiting for {", ".join(missing_relations)} relation' + if "," in self.message: + self.message += "s" + + +class RelationDefinition: + def __init__(self, relation_name: str, keys: List, source_type): + if source_type != Application and source_type != Unit: + raise TypeError( + "source_type should be ops.model.Application or ops.model.Unit" + ) + self.relation_name = relation_name + self.keys = keys + self.source_type = source_type + + +def check_missing_relation_data( + data: Dict, + expected_relations_data: List[RelationDefinition], +): + missing_relations = [] + for relation_data in expected_relations_data: + if not all( + f"{relation_data.relation_name}_{k}" in data for k in relation_data.keys + ): + missing_relations.append(relation_data.relation_name) + if missing_relations: + raise RelationsMissing(missing_relations) + + +def get_relation_data( + charm: CharmBase, + relation_data: RelationDefinition, +) -> Dict: + data = {} + relation = charm.model.get_relation(relation_data.relation_name) + if relation: + self_app_unit = ( + charm.app if relation_data.source_type == Application else charm.unit + ) + expected_type = relation_data.source_type + for app_unit in relation.data: + if app_unit != self_app_unit and isinstance(app_unit, expected_type): + if all(k in relation.data[app_unit] for k in relation_data.keys): + for k in relation_data.keys: + data[f"{relation_data.relation_name}_{k}"] = relation.data[ + app_unit + ].get(k) + break + return data + + +class MongodbExporterCharm(CharmBase): + """Mongodb Exporter Charm.""" + + state = StoredState() + + def __init__(self, *args) -> NoReturn: + """Mongodb Exporter Charm constructor.""" + super().__init__(*args) + + # Internal state initialization + self.state.set_default(pod_spec=None) + + self.port = MONGODB_EXPORTER_PORT + self.image = OCIImageResource(self, "image") + + # Registering regular events + self.framework.observe(self.on.start, self.configure_pod) + self.framework.observe(self.on.config_changed, self.configure_pod) + + # Registering required relation events + self.framework.observe(self.on.mongodb_relation_changed, self.configure_pod) + + # Registering required relation departed events + self.framework.observe(self.on.mongodb_relation_departed, self.configure_pod) + + # Registering provided relation events + self.framework.observe( + self.on.prometheus_scrape_relation_joined, self._publish_scrape_info + ) + self.framework.observe( + self.on.grafana_dashboard_relation_joined, self._publish_dashboard_info + ) + + def _publish_scrape_info(self, event: EventBase) -> NoReturn: + """Publishes scrape information. + + Args: + event (EventBase): Exporter relation event. + """ + rel_data = { + "hostname": urlparse(self.model.config["site_url"]).hostname + if self.model.config["site_url"] + else self.model.app.name, + "port": "80" + if self.model.config["site_url"] + else str(MONGODB_EXPORTER_PORT), + "metrics_path": "/metrics", + "scrape_interval": "30s", + "scrape_timeout": "15s", + } + for k, v in rel_data.items(): + event.relation.data[self.unit][k] = v + + def _publish_dashboard_info(self, event: EventBase) -> NoReturn: + """Publishes dashboard information. + + Args: + event (EventBase): Exporter relation event. + """ + rel_data = { + "name": "osm-mongodb", + "dashboard": Path("files/mongodb_exporter_dashboard.json").read_text(), + } + for k, v in rel_data.items(): + event.relation.data[self.unit][k] = v + + @property + def relations_requirements(self): + return [RelationDefinition("mongodb", ["connection_string"], Unit)] + + def get_relation_state(self): + relation_state = {} + for relation_requirements in self.relations_requirements: + data = get_relation_data(self, relation_requirements) + relation_state = {**relation_state, **data} + check_missing_relation_data(relation_state, self.relations_requirements) + return relation_state + + def configure_pod(self, _=None) -> NoReturn: + """Assemble the pod spec and apply it, if possible. + + Args: + event (EventBase): Hook or Relation event that started the + function. + """ + if not self.unit.is_leader(): + self.unit.status = ActiveStatus("ready") + return + + relation_state = None + try: + relation_state = self.get_relation_state() + except RelationsMissing as exc: + logger.exception("Relation missing error") + self.unit.status = BlockedStatus(exc.message) + return + + self.unit.status = MaintenanceStatus("Assembling pod spec") + + # Fetch image information + try: + self.unit.status = MaintenanceStatus("Fetching image information") + image_info = self.image.fetch() + except OCIImageResourceError: + self.unit.status = BlockedStatus("Error fetching image information") + return + + try: + pod_spec = make_pod_spec( + image_info, + self.model.config, + relation_state, + self.model.app.name, + self.port, + ) + except ValueError as exc: + logger.exception("Config/Relation data validation error") + self.unit.status = BlockedStatus(str(exc)) + return + + if self.state.pod_spec != pod_spec: + self.model.pod.set_spec(pod_spec) + self.state.pod_spec = pod_spec + + self.unit.status = ActiveStatus("ready") + + +if __name__ == "__main__": + main(MongodbExporterCharm) diff --git a/installers/charm/mongodb-exporter/src/pod_spec.py b/installers/charm/mongodb-exporter/src/pod_spec.py new file mode 100644 index 00000000..781c6892 --- /dev/null +++ b/installers/charm/mongodb-exporter/src/pod_spec.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +import logging +from ipaddress import ip_network +from typing import Any, Dict, List +from urllib.parse import urlparse + +logger = logging.getLogger(__name__) + + +def _validate_ip_network(network: str) -> bool: + """Validate IP network. + + Args: + network (str): IP network range. + + Returns: + bool: True if valid, false otherwise. + """ + if not network: + return True + + try: + ip_network(network) + except ValueError: + return False + + return True + + +def _validate_data(config_data: Dict[str, Any], relation_data: Dict[str, Any]) -> bool: + """Validates passed information. + + Args: + config_data (Dict[str, Any]): configuration information. + relation_data (Dict[str, Any]): relation information + + Raises: + ValueError: when config and/or relation data is not valid. + """ + config_validators = { + "site_url": lambda value, _: isinstance(value, str) + if value is not None + else True, + "ingress_whitelist_source_range": lambda value, _: _validate_ip_network(value), + "tls_secret_name": lambda value, _: isinstance(value, str) + if value is not None + else True, + } + relation_validators = { + "mongodb_connection_string": lambda value, _: ( + isinstance(value, str) and value.startswith("mongodb://") + ) + } + problems = [] + + for key, validator in config_validators.items(): + valid = validator(config_data.get(key), config_data) + + if not valid: + problems.append(key) + + for key, validator in relation_validators.items(): + valid = validator(relation_data.get(key), relation_data) + + if not valid: + problems.append(key) + + if len(problems) > 0: + raise ValueError("Errors found in: {}".format(", ".join(problems))) + + return True + + +def _make_pod_ports(port: int) -> List[Dict[str, Any]]: + """Generate pod ports details. + + Args: + port (int): port to expose. + + Returns: + List[Dict[str, Any]]: pod port details. + """ + return [ + { + "name": "mongo-exporter", + "containerPort": port, + "protocol": "TCP", + } + ] + + +def _make_pod_envconfig( + config: Dict[str, Any], relation_state: Dict[str, Any] +) -> Dict[str, Any]: + """Generate pod environment configuration. + + Args: + config (Dict[str, Any]): configuration information. + relation_state (Dict[str, Any]): relation state information. + + Returns: + Dict[str, Any]: pod environment configuration. + """ + envconfig = { + "MONGODB_URI": relation_state.get("mongodb_connection_string"), + } + + return envconfig + + +def _make_pod_ingress_resources( + config: Dict[str, Any], app_name: str, port: int +) -> List[Dict[str, Any]]: + """Generate pod ingress resources. + + Args: + config (Dict[str, Any]): configuration information. + app_name (str): application name. + port (int): port to expose. + + Returns: + List[Dict[str, Any]]: pod ingress resources. + """ + site_url = config.get("site_url") + + if not site_url: + return + + parsed = urlparse(site_url) + + if not parsed.scheme.startswith("http"): + return + + ingress_whitelist_source_range = config["ingress_whitelist_source_range"] + annotations = {} + + if ingress_whitelist_source_range: + annotations[ + "nginx.ingress.kubernetes.io/whitelist-source-range" + ] = ingress_whitelist_source_range + + ingress_spec_tls = None + + if parsed.scheme == "https": + ingress_spec_tls = [{"hosts": [parsed.hostname]}] + tls_secret_name = config["tls_secret_name"] + if tls_secret_name: + ingress_spec_tls[0]["secretName"] = tls_secret_name + else: + annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false" + + ingress = { + "name": "{}-ingress".format(app_name), + "annotations": annotations, + "spec": { + "rules": [ + { + "host": parsed.hostname, + "http": { + "paths": [ + { + "path": "/", + "backend": { + "serviceName": app_name, + "servicePort": port, + }, + } + ] + }, + } + ] + }, + } + if ingress_spec_tls: + ingress["spec"]["tls"] = ingress_spec_tls + + return [ingress] + + +def _make_readiness_probe(port: int) -> Dict[str, Any]: + """Generate readiness probe. + + Args: + port (int): service port. + + Returns: + Dict[str, Any]: readiness probe. + """ + return { + "httpGet": { + "path": "/api/health", + "port": port, + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "timeoutSeconds": 5, + "successThreshold": 1, + "failureThreshold": 3, + } + + +def _make_liveness_probe(port: int) -> Dict[str, Any]: + """Generate liveness probe. + + Args: + port (int): service port. + + Returns: + Dict[str, Any]: liveness probe. + """ + return { + "httpGet": { + "path": "/api/health", + "port": port, + }, + "initialDelaySeconds": 60, + "timeoutSeconds": 30, + "failureThreshold": 10, + } + + +def make_pod_spec( + image_info: Dict[str, str], + config: Dict[str, Any], + relation_state: Dict[str, Any], + app_name: str = "mongodb-exporter", + port: int = 9216, +) -> Dict[str, Any]: + """Generate the pod spec information. + + Args: + image_info (Dict[str, str]): Object provided by + OCIImageResource("image").fetch(). + config (Dict[str, Any]): Configuration information. + relation_state (Dict[str, Any]): Relation state information. + app_name (str, optional): Application name. Defaults to "ro". + port (int, optional): Port for the container. Defaults to 9090. + + Returns: + Dict[str, Any]: Pod spec dictionary for the charm. + """ + if not image_info: + return None + + _validate_data(config, relation_state) + + ports = _make_pod_ports(port) + env_config = _make_pod_envconfig(config, relation_state) + readiness_probe = _make_readiness_probe(port) + liveness_probe = _make_liveness_probe(port) + ingress_resources = _make_pod_ingress_resources(config, app_name, port) + + return { + "version": 3, + "containers": [ + { + "name": app_name, + "imageDetails": image_info, + "imagePullPolicy": "Always", + "ports": ports, + "envConfig": env_config, + "kubernetes": { + "readinessProbe": readiness_probe, + "livenessProbe": liveness_probe, + }, + } + ], + "kubernetesResources": { + "ingressResources": ingress_resources or [], + }, + } diff --git a/installers/charm/mongodb-exporter/tests/__init__.py b/installers/charm/mongodb-exporter/tests/__init__.py new file mode 100644 index 00000000..4fd849a5 --- /dev/null +++ b/installers/charm/mongodb-exporter/tests/__init__.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +"""Init mocking for unit tests.""" + +import sys +import mock + +sys.path.append("src") + +oci_image = mock.MagicMock() +sys.modules["oci_image"] = oci_image diff --git a/installers/charm/mongodb-exporter/tests/test_charm.py b/installers/charm/mongodb-exporter/tests/test_charm.py new file mode 100644 index 00000000..372886b4 --- /dev/null +++ b/installers/charm/mongodb-exporter/tests/test_charm.py @@ -0,0 +1,495 @@ +#!/usr/bin/env python3 +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +from typing import NoReturn +import unittest + +from ops.model import BlockedStatus +from ops.testing import Harness + +from charm import MongodbExporterCharm + + +class TestCharm(unittest.TestCase): + """Mongodb Exporter Charm unit tests.""" + + def setUp(self) -> NoReturn: + """Test setup""" + self.harness = Harness(MongodbExporterCharm) + self.harness.set_leader(is_leader=True) + self.harness.begin() + + def test_on_start_without_relations(self) -> NoReturn: + """Test installation without any relation.""" + self.harness.charm.on.start.emit() + + # Verifying status + self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) + + # Verifying status message + self.assertGreater(len(self.harness.charm.unit.status.message), 0) + self.assertTrue( + self.harness.charm.unit.status.message.startswith("Waiting for ") + ) + self.assertIn("mongodb", self.harness.charm.unit.status.message) + self.assertTrue(self.harness.charm.unit.status.message.endswith(" relation")) + + def test_on_start_with_relations_without_http(self) -> NoReturn: + """Test deployment.""" + expected_result = { + "version": 3, + "containers": [ + { + "name": "mongodb-exporter", + "imageDetails": self.harness.charm.image.fetch(), + "imagePullPolicy": "Always", + "ports": [ + { + "name": "mongo-exporter", + "containerPort": 9216, + "protocol": "TCP", + } + ], + "envConfig": { + "MONGODB_URI": "mongodb://mongo", + }, + "kubernetes": { + "readinessProbe": { + "httpGet": { + "path": "/api/health", + "port": 9216, + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "timeoutSeconds": 5, + "successThreshold": 1, + "failureThreshold": 3, + }, + "livenessProbe": { + "httpGet": { + "path": "/api/health", + "port": 9216, + }, + "initialDelaySeconds": 60, + "timeoutSeconds": 30, + "failureThreshold": 10, + }, + }, + }, + ], + "kubernetesResources": {"ingressResources": []}, + } + + self.harness.charm.on.start.emit() + + # Initializing the mongodb relation + relation_id = self.harness.add_relation("mongodb", "mongodb") + self.harness.add_relation_unit(relation_id, "mongodb/0") + self.harness.update_relation_data( + relation_id, + "mongodb/0", + { + "connection_string": "mongodb://mongo", + }, + ) + + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + + pod_spec, _ = self.harness.get_pod_spec() + + self.assertDictEqual(expected_result, pod_spec) + + def test_ingress_resources_with_http(self) -> NoReturn: + """Test ingress resources with HTTP.""" + expected_result = { + "version": 3, + "containers": [ + { + "name": "mongodb-exporter", + "imageDetails": self.harness.charm.image.fetch(), + "imagePullPolicy": "Always", + "ports": [ + { + "name": "mongo-exporter", + "containerPort": 9216, + "protocol": "TCP", + } + ], + "envConfig": { + "MONGODB_URI": "mongodb://mongo", + }, + "kubernetes": { + "readinessProbe": { + "httpGet": { + "path": "/api/health", + "port": 9216, + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "timeoutSeconds": 5, + "successThreshold": 1, + "failureThreshold": 3, + }, + "livenessProbe": { + "httpGet": { + "path": "/api/health", + "port": 9216, + }, + "initialDelaySeconds": 60, + "timeoutSeconds": 30, + "failureThreshold": 10, + }, + }, + }, + ], + "kubernetesResources": { + "ingressResources": [ + { + "name": "mongodb-exporter-ingress", + "annotations": { + "nginx.ingress.kubernetes.io/ssl-redirect": "false", + }, + "spec": { + "rules": [ + { + "host": "mongodb-exporter", + "http": { + "paths": [ + { + "path": "/", + "backend": { + "serviceName": "mongodb-exporter", + "servicePort": 9216, + }, + } + ] + }, + } + ] + }, + } + ], + }, + } + + self.harness.charm.on.start.emit() + + # Initializing the mongodb relation + relation_id = self.harness.add_relation("mongodb", "mongodb") + self.harness.add_relation_unit(relation_id, "mongodb/0") + self.harness.update_relation_data( + relation_id, + "mongodb/0", + { + "connection_string": "mongodb://mongo", + }, + ) + + self.harness.update_config({"site_url": "http://mongodb-exporter"}) + + pod_spec, _ = self.harness.get_pod_spec() + + self.assertDictEqual(expected_result, pod_spec) + + def test_ingress_resources_with_https(self) -> NoReturn: + """Test ingress resources with HTTPS.""" + expected_result = { + "version": 3, + "containers": [ + { + "name": "mongodb-exporter", + "imageDetails": self.harness.charm.image.fetch(), + "imagePullPolicy": "Always", + "ports": [ + { + "name": "mongo-exporter", + "containerPort": 9216, + "protocol": "TCP", + } + ], + "envConfig": { + "MONGODB_URI": "mongodb://mongo", + }, + "kubernetes": { + "readinessProbe": { + "httpGet": { + "path": "/api/health", + "port": 9216, + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "timeoutSeconds": 5, + "successThreshold": 1, + "failureThreshold": 3, + }, + "livenessProbe": { + "httpGet": { + "path": "/api/health", + "port": 9216, + }, + "initialDelaySeconds": 60, + "timeoutSeconds": 30, + "failureThreshold": 10, + }, + }, + }, + ], + "kubernetesResources": { + "ingressResources": [ + { + "name": "mongodb-exporter-ingress", + "annotations": {}, + "spec": { + "rules": [ + { + "host": "mongodb-exporter", + "http": { + "paths": [ + { + "path": "/", + "backend": { + "serviceName": "mongodb-exporter", + "servicePort": 9216, + }, + } + ] + }, + } + ], + "tls": [ + { + "hosts": ["mongodb-exporter"], + "secretName": "mongodb-exporter", + } + ], + }, + } + ], + }, + } + + self.harness.charm.on.start.emit() + + # Initializing the mongodb relation + relation_id = self.harness.add_relation("mongodb", "mongodb") + self.harness.add_relation_unit(relation_id, "mongodb/0") + self.harness.update_relation_data( + relation_id, + "mongodb/0", + { + "connection_string": "mongodb://mongo", + }, + ) + + self.harness.update_config( + { + "site_url": "https://mongodb-exporter", + "tls_secret_name": "mongodb-exporter", + } + ) + + pod_spec, _ = self.harness.get_pod_spec() + + self.assertDictEqual(expected_result, pod_spec) + + def test_ingress_resources_with_https_and_ingress_whitelist(self) -> NoReturn: + """Test ingress resources with HTTPS and ingress whitelist.""" + expected_result = { + "version": 3, + "containers": [ + { + "name": "mongodb-exporter", + "imageDetails": self.harness.charm.image.fetch(), + "imagePullPolicy": "Always", + "ports": [ + { + "name": "mongo-exporter", + "containerPort": 9216, + "protocol": "TCP", + } + ], + "envConfig": { + "MONGODB_URI": "mongodb://mongo", + }, + "kubernetes": { + "readinessProbe": { + "httpGet": { + "path": "/api/health", + "port": 9216, + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "timeoutSeconds": 5, + "successThreshold": 1, + "failureThreshold": 3, + }, + "livenessProbe": { + "httpGet": { + "path": "/api/health", + "port": 9216, + }, + "initialDelaySeconds": 60, + "timeoutSeconds": 30, + "failureThreshold": 10, + }, + }, + }, + ], + "kubernetesResources": { + "ingressResources": [ + { + "name": "mongodb-exporter-ingress", + "annotations": { + "nginx.ingress.kubernetes.io/whitelist-source-range": "0.0.0.0/0", + }, + "spec": { + "rules": [ + { + "host": "mongodb-exporter", + "http": { + "paths": [ + { + "path": "/", + "backend": { + "serviceName": "mongodb-exporter", + "servicePort": 9216, + }, + } + ] + }, + } + ], + "tls": [ + { + "hosts": ["mongodb-exporter"], + "secretName": "mongodb-exporter", + } + ], + }, + } + ], + }, + } + + self.harness.charm.on.start.emit() + + # Initializing the mongodb relation + relation_id = self.harness.add_relation("mongodb", "mongodb") + self.harness.add_relation_unit(relation_id, "mongodb/0") + self.harness.update_relation_data( + relation_id, + "mongodb/0", + { + "connection_string": "mongodb://mongo", + }, + ) + + self.harness.update_config( + { + "site_url": "https://mongodb-exporter", + "tls_secret_name": "mongodb-exporter", + "ingress_whitelist_source_range": "0.0.0.0/0", + } + ) + + pod_spec, _ = self.harness.get_pod_spec() + + self.assertDictEqual(expected_result, pod_spec) + + def test_on_mongodb_unit_relation_changed(self) -> NoReturn: + """Test to see if mongodb relation is updated.""" + self.harness.charm.on.start.emit() + + # Initializing the mongodb relation + relation_id = self.harness.add_relation("mongodb", "mongodb") + self.harness.add_relation_unit(relation_id, "mongodb/0") + self.harness.update_relation_data( + relation_id, + "mongodb/0", + { + "connection_string": "mongodb://mongo", + }, + ) + + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + + def test_publish_scrape_info(self) -> NoReturn: + """Test to see if scrape relation is updated.""" + expected_result = { + "hostname": "mongodb-exporter", + "port": "9216", + "metrics_path": "/metrics", + "scrape_interval": "30s", + "scrape_timeout": "15s", + } + + self.harness.charm.on.start.emit() + + relation_id = self.harness.add_relation("prometheus-scrape", "prometheus") + self.harness.add_relation_unit(relation_id, "prometheus/0") + relation_data = self.harness.get_relation_data( + relation_id, "mongodb-exporter/0" + ) + + self.assertDictEqual(expected_result, relation_data) + + def test_publish_scrape_info_with_site_url(self) -> NoReturn: + """Test to see if target relation is updated.""" + expected_result = { + "hostname": "mongodb-exporter-osm", + "port": "80", + "metrics_path": "/metrics", + "scrape_interval": "30s", + "scrape_timeout": "15s", + } + + self.harness.charm.on.start.emit() + + self.harness.update_config({"site_url": "http://mongodb-exporter-osm"}) + + relation_id = self.harness.add_relation("prometheus-scrape", "prometheus") + self.harness.add_relation_unit(relation_id, "prometheus/0") + relation_data = self.harness.get_relation_data( + relation_id, "mongodb-exporter/0" + ) + + self.assertDictEqual(expected_result, relation_data) + + def test_publish_dashboard_info(self) -> NoReturn: + """Test to see if dashboard relation is updated.""" + self.harness.charm.on.start.emit() + + relation_id = self.harness.add_relation("grafana-dashboard", "grafana") + self.harness.add_relation_unit(relation_id, "grafana/0") + relation_data = self.harness.get_relation_data( + relation_id, "mongodb-exporter/0" + ) + + self.assertEqual("osm-mongodb", relation_data["name"]) + self.assertTrue("dashboard" in relation_data) + self.assertTrue(len(relation_data["dashboard"]) > 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/installers/charm/mongodb-exporter/tests/test_pod_spec.py b/installers/charm/mongodb-exporter/tests/test_pod_spec.py new file mode 100644 index 00000000..3e312f48 --- /dev/null +++ b/installers/charm/mongodb-exporter/tests/test_pod_spec.py @@ -0,0 +1,478 @@ +#!/usr/bin/env python3 +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +from typing import NoReturn +import unittest + +import pod_spec + + +class TestPodSpec(unittest.TestCase): + """Pod spec unit tests.""" + + def test_make_pod_ports(self) -> NoReturn: + """Testing make pod ports.""" + port = 9216 + + expected_result = [ + { + "name": "mongo-exporter", + "containerPort": port, + "protocol": "TCP", + } + ] + + pod_ports = pod_spec._make_pod_ports(port) + + self.assertListEqual(expected_result, pod_ports) + + def test_make_pod_envconfig(self) -> NoReturn: + """Teting make pod envconfig.""" + config = {} + relation_state = { + "mongodb_connection_string": "mongodb://mongo", + } + + expected_result = {"MONGODB_URI": "mongodb://mongo"} + + pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state) + + self.assertDictEqual(expected_result, pod_envconfig) + + def test_make_pod_ingress_resources_without_site_url(self) -> NoReturn: + """Testing make pod ingress resources without site_url.""" + config = {"site_url": ""} + app_name = "mongodb-exporter" + port = 9216 + + pod_ingress_resources = pod_spec._make_pod_ingress_resources( + config, app_name, port + ) + + self.assertIsNone(pod_ingress_resources) + + def test_make_pod_ingress_resources(self) -> NoReturn: + """Testing make pod ingress resources.""" + config = { + "site_url": "http://mongodb-exporter", + "ingress_whitelist_source_range": "", + } + app_name = "mongodb-exporter" + port = 9216 + + expected_result = [ + { + "name": f"{app_name}-ingress", + "annotations": { + "nginx.ingress.kubernetes.io/ssl-redirect": "false", + }, + "spec": { + "rules": [ + { + "host": app_name, + "http": { + "paths": [ + { + "path": "/", + "backend": { + "serviceName": app_name, + "servicePort": port, + }, + } + ] + }, + } + ] + }, + } + ] + + pod_ingress_resources = pod_spec._make_pod_ingress_resources( + config, app_name, port + ) + + self.assertListEqual(expected_result, pod_ingress_resources) + + def test_make_pod_ingress_resources_with_whitelist_source_range(self) -> NoReturn: + """Testing make pod ingress resources with whitelist_source_range.""" + config = { + "site_url": "http://mongodb-exporter", + "ingress_whitelist_source_range": "0.0.0.0/0", + } + app_name = "mongodb-exporter" + port = 9216 + + expected_result = [ + { + "name": f"{app_name}-ingress", + "annotations": { + "nginx.ingress.kubernetes.io/ssl-redirect": "false", + "nginx.ingress.kubernetes.io/whitelist-source-range": config[ + "ingress_whitelist_source_range" + ], + }, + "spec": { + "rules": [ + { + "host": app_name, + "http": { + "paths": [ + { + "path": "/", + "backend": { + "serviceName": app_name, + "servicePort": port, + }, + } + ] + }, + } + ] + }, + } + ] + + pod_ingress_resources = pod_spec._make_pod_ingress_resources( + config, app_name, port + ) + + self.assertListEqual(expected_result, pod_ingress_resources) + + def test_make_pod_ingress_resources_with_https(self) -> NoReturn: + """Testing make pod ingress resources with HTTPs.""" + config = { + "site_url": "https://mongodb-exporter", + "ingress_whitelist_source_range": "", + "tls_secret_name": "", + } + app_name = "mongodb-exporter" + port = 9216 + + expected_result = [ + { + "name": f"{app_name}-ingress", + "annotations": {}, + "spec": { + "rules": [ + { + "host": app_name, + "http": { + "paths": [ + { + "path": "/", + "backend": { + "serviceName": app_name, + "servicePort": port, + }, + } + ] + }, + } + ], + "tls": [{"hosts": [app_name]}], + }, + } + ] + + pod_ingress_resources = pod_spec._make_pod_ingress_resources( + config, app_name, port + ) + + self.assertListEqual(expected_result, pod_ingress_resources) + + def test_make_pod_ingress_resources_with_https_tls_secret_name(self) -> NoReturn: + """Testing make pod ingress resources with HTTPs and TLS secret name.""" + config = { + "site_url": "https://mongodb-exporter", + "ingress_whitelist_source_range": "", + "tls_secret_name": "secret_name", + } + app_name = "mongodb-exporter" + port = 9216 + + expected_result = [ + { + "name": f"{app_name}-ingress", + "annotations": {}, + "spec": { + "rules": [ + { + "host": app_name, + "http": { + "paths": [ + { + "path": "/", + "backend": { + "serviceName": app_name, + "servicePort": port, + }, + } + ] + }, + } + ], + "tls": [ + {"hosts": [app_name], "secretName": config["tls_secret_name"]} + ], + }, + } + ] + + pod_ingress_resources = pod_spec._make_pod_ingress_resources( + config, app_name, port + ) + + self.assertListEqual(expected_result, pod_ingress_resources) + + def test_make_readiness_probe(self) -> NoReturn: + """Testing make readiness probe.""" + port = 9216 + + expected_result = { + "httpGet": { + "path": "/api/health", + "port": port, + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "timeoutSeconds": 5, + "successThreshold": 1, + "failureThreshold": 3, + } + + readiness_probe = pod_spec._make_readiness_probe(port) + + self.assertDictEqual(expected_result, readiness_probe) + + def test_make_liveness_probe(self) -> NoReturn: + """Testing make liveness probe.""" + port = 9216 + + expected_result = { + "httpGet": { + "path": "/api/health", + "port": port, + }, + "initialDelaySeconds": 60, + "timeoutSeconds": 30, + "failureThreshold": 10, + } + + liveness_probe = pod_spec._make_liveness_probe(port) + + self.assertDictEqual(expected_result, liveness_probe) + + def test_make_pod_spec(self) -> NoReturn: + """Testing make pod spec.""" + image_info = {"upstream-source": "bitnami/mongodb-exporter:latest"} + config = { + "site_url": "", + } + relation_state = { + "mongodb_connection_string": "mongodb://mongo", + } + app_name = "mongodb-exporter" + port = 9216 + + expected_result = { + "version": 3, + "containers": [ + { + "name": app_name, + "imageDetails": image_info, + "imagePullPolicy": "Always", + "ports": [ + { + "name": "mongo-exporter", + "containerPort": port, + "protocol": "TCP", + } + ], + "envConfig": { + "MONGODB_URI": "mongodb://mongo", + }, + "kubernetes": { + "readinessProbe": { + "httpGet": { + "path": "/api/health", + "port": port, + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "timeoutSeconds": 5, + "successThreshold": 1, + "failureThreshold": 3, + }, + "livenessProbe": { + "httpGet": { + "path": "/api/health", + "port": port, + }, + "initialDelaySeconds": 60, + "timeoutSeconds": 30, + "failureThreshold": 10, + }, + }, + } + ], + "kubernetesResources": {"ingressResources": []}, + } + + spec = pod_spec.make_pod_spec( + image_info, config, relation_state, app_name, port + ) + + self.assertDictEqual(expected_result, spec) + + def test_make_pod_spec_with_ingress(self) -> NoReturn: + """Testing make pod spec.""" + image_info = {"upstream-source": "bitnami/mongodb-exporter:latest"} + config = { + "site_url": "https://mongodb-exporter", + "tls_secret_name": "mongodb-exporter", + "ingress_whitelist_source_range": "0.0.0.0/0", + } + relation_state = { + "mongodb_connection_string": "mongodb://mongo", + } + app_name = "mongodb-exporter" + port = 9216 + + expected_result = { + "version": 3, + "containers": [ + { + "name": app_name, + "imageDetails": image_info, + "imagePullPolicy": "Always", + "ports": [ + { + "name": "mongo-exporter", + "containerPort": port, + "protocol": "TCP", + } + ], + "envConfig": { + "MONGODB_URI": "mongodb://mongo", + }, + "kubernetes": { + "readinessProbe": { + "httpGet": { + "path": "/api/health", + "port": port, + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "timeoutSeconds": 5, + "successThreshold": 1, + "failureThreshold": 3, + }, + "livenessProbe": { + "httpGet": { + "path": "/api/health", + "port": port, + }, + "initialDelaySeconds": 60, + "timeoutSeconds": 30, + "failureThreshold": 10, + }, + }, + } + ], + "kubernetesResources": { + "ingressResources": [ + { + "name": "{}-ingress".format(app_name), + "annotations": { + "nginx.ingress.kubernetes.io/whitelist-source-range": config.get( + "ingress_whitelist_source_range" + ), + }, + "spec": { + "rules": [ + { + "host": app_name, + "http": { + "paths": [ + { + "path": "/", + "backend": { + "serviceName": app_name, + "servicePort": port, + }, + } + ] + }, + } + ], + "tls": [ + { + "hosts": [app_name], + "secretName": config.get("tls_secret_name"), + } + ], + }, + } + ], + }, + } + + spec = pod_spec.make_pod_spec( + image_info, config, relation_state, app_name, port + ) + + self.assertDictEqual(expected_result, spec) + + def test_make_pod_spec_without_image_info(self) -> NoReturn: + """Testing make pod spec without image_info.""" + image_info = None + config = { + "site_url": "", + } + relation_state = { + "mongodb_connection_string": "mongodb://mongo", + } + app_name = "mongodb-exporter" + port = 9216 + + spec = pod_spec.make_pod_spec( + image_info, config, relation_state, app_name, port + ) + + self.assertIsNone(spec) + + def test_make_pod_spec_without_relation_state(self) -> NoReturn: + """Testing make pod spec without relation_state.""" + image_info = {"upstream-source": "bitnami/mongodb-exporter:latest"} + config = { + "site_url": "", + } + relation_state = {} + app_name = "mongodb-exporter" + port = 9216 + + with self.assertRaises(ValueError): + pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port) + + +if __name__ == "__main__": + unittest.main() diff --git a/installers/charm/mongodb-exporter/tox.ini b/installers/charm/mongodb-exporter/tox.ini new file mode 100644 index 00000000..69911728 --- /dev/null +++ b/installers/charm/mongodb-exporter/tox.ini @@ -0,0 +1,81 @@ +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## + +[tox] +skipsdist = True +envlist = unit, lint +sitepackages = False +skip_missing_interpreters = False + +[testenv] +basepython = python3 +setenv = + PYTHONHASHSEED=0 + PYTHONPATH = {toxinidir}/src + CHARM_NAME = mongodb-exporter + +[testenv:build] +passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY +whitelist_externals = + charmcraft + rm + unzip +commands = + rm -rf release mongodb-exporter.charm + charmcraft build + unzip mongodb-exporter.charm -d release + +[testenv:unit] +commands = + coverage erase + stestr run --slowest --test-path=./tests --top-dir=./ + coverage combine + coverage html -d cover + coverage xml -o cover/coverage.xml + coverage report +deps = + coverage + stestr + mock + ops +setenv = + {[testenv]setenv} + PYTHON=coverage run + +[testenv:lint] +deps = + black + yamllint + flake8 +commands = + black --check --diff . --exclude "build/|.tox/|mod/|lib/" + yamllint . + flake8 . --max-line-length=100 --ignore="E501,W503,W504,F722" --exclude "build/ .tox/ mod/ lib/" + +[coverage:run] +branch = True +concurrency = multiprocessing +parallel = True +source = + . +omit = + .tox/* + tests/* diff --git a/installers/charm/prometheus-mongodb-exporter/.gitignore b/installers/charm/prometheus-mongodb-exporter/.gitignore deleted file mode 100644 index 60e34f39..00000000 --- a/installers/charm/prometheus-mongodb-exporter/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -venv -.vscode -build -prometheus-mongodb-exporter.charm -.coverage -.stestr -cover diff --git a/installers/charm/prometheus-mongodb-exporter/.yamllint.yaml b/installers/charm/prometheus-mongodb-exporter/.yamllint.yaml deleted file mode 100644 index f300159a..00000000 --- a/installers/charm/prometheus-mongodb-exporter/.yamllint.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - ---- -extends: default - -yaml-files: - - "*.yaml" - - "*.yml" - - ".yamllint" -ignore: | - .tox - build/ diff --git a/installers/charm/prometheus-mongodb-exporter/README.md b/installers/charm/prometheus-mongodb-exporter/README.md deleted file mode 100644 index 84df4c97..00000000 --- a/installers/charm/prometheus-mongodb-exporter/README.md +++ /dev/null @@ -1,23 +0,0 @@ - - -# Prometheus Mongodb Exporter operator Charm for Kubernetes - -## Requirements diff --git a/installers/charm/prometheus-mongodb-exporter/config.yaml b/installers/charm/prometheus-mongodb-exporter/config.yaml deleted file mode 100644 index a3aaa21a..00000000 --- a/installers/charm/prometheus-mongodb-exporter/config.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -options: - ingress_whitelist_source_range: - type: string - description: | - A comma-separated list of CIDRs to store in the - ingress.kubernetes.io/whitelist-source-range annotation. - - This can be used to lock down access to - Keystone based on source IP address. - default: "" - tls_secret_name: - type: string - description: TLS Secret name - default: "" - site_url: - type: string - description: Ingress URL - default: "" diff --git a/installers/charm/prometheus-mongodb-exporter/files/mongodb_exporter_dashboard.yaml b/installers/charm/prometheus-mongodb-exporter/files/mongodb_exporter_dashboard.yaml deleted file mode 100644 index b2489f4a..00000000 --- a/installers/charm/prometheus-mongodb-exporter/files/mongodb_exporter_dashboard.yaml +++ /dev/null @@ -1,691 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - ---- -annotations: - list: - - builtIn: 1 - datasource: '-- Grafana --' - enable: true - hide: true - iconColor: 'rgba(0, 211, 255, 1)' - name: Annotations & Alerts - type: dashboard -description: MongoDB Prometheus Exporter Dashboard. -editable: true -gnetId: 2583 -graphTooltip: 1 -id: 9 -iteration: 1577555358996 -links: [] -panels: - - collapsed: false - gridPos: - h: 1 - w: 24 - x: 0 - 'y': 0 - id: 22 - panels: [] - repeat: env - title: Health - type: row - - cacheTimeout: null - colorBackground: false - colorValue: true - colors: - - 'rgba(245, 54, 54, 0.9)' - - 'rgba(237, 129, 40, 0.89)' - - 'rgba(50, 172, 45, 0.97)' - datasource: Prometheus - decimals: null - format: s - gauge: - maxValue: 100 - minValue: 0 - show: false - thresholdLabels: false - thresholdMarkers: true - gridPos: - h: 4 - w: 12 - x: 0 - 'y': 1 - id: 10 - interval: null - links: [] - mappingType: 1 - mappingTypes: - - name: value to text - value: 1 - - name: range to text - value: 2 - maxDataPoints: 100 - nullPointMode: connected - nullText: null - options: {} - postfix: '' - postfixFontSize: 50% - prefix: '' - prefixFontSize: 50% - rangeMaps: - - from: 'null' - text: N/A - to: 'null' - sparkline: - fillColor: 'rgba(31, 118, 189, 0.18)' - full: false - lineColor: 'rgb(31, 120, 193)' - show: false - tableColumn: '' - targets: - - expr: 'mongodb_instance_uptime_seconds{instance=~"$instance"}' - format: time_series - intervalFactor: 2 - legendFormat: '' - refId: A - step: 1800 - thresholds: '0,360' - title: Uptime - type: singlestat - valueFontSize: 80% - valueMaps: - - op: '=' - text: N/A - value: 'null' - valueName: current - - cacheTimeout: null - colorBackground: false - colorValue: false - colors: - - 'rgba(245, 54, 54, 0.9)' - - 'rgba(237, 129, 40, 0.89)' - - 'rgba(50, 172, 45, 0.97)' - datasource: Prometheus - format: none - gauge: - maxValue: 100 - minValue: 0 - show: false - thresholdLabels: false - thresholdMarkers: true - gridPos: - h: 4 - w: 12 - x: 12 - 'y': 1 - id: 1 - interval: null - links: [] - mappingType: 1 - mappingTypes: - - name: value to text - value: 1 - - name: range to text - value: 2 - maxDataPoints: 100 - nullPointMode: connected - nullText: null - options: {} - postfix: '' - postfixFontSize: 50% - prefix: '' - prefixFontSize: 50% - rangeMaps: - - from: 'null' - text: N/A - to: 'null' - sparkline: - fillColor: 'rgba(31, 118, 189, 0.18)' - full: true - lineColor: 'rgb(31, 120, 193)' - show: true - tableColumn: '' - targets: - - expr: 'mongodb_connections{instance=~"$instance",state="current"}' - format: time_series - intervalFactor: 2 - legendFormat: '' - metric: mongodb_connections - refId: A - step: 1800 - thresholds: '' - title: Open Connections - type: singlestat - valueFontSize: 80% - valueMaps: - - op: '=' - text: N/A - value: 'null' - valueName: avg - - collapsed: false - gridPos: - h: 1 - w: 24 - x: 0 - 'y': 5 - id: 20 - panels: [] - repeat: env - title: Operations - type: row - - aliasColors: {} - bars: false - dashLength: 10 - dashes: false - datasource: Prometheus - fill: 1 - fillGradient: 0 - gridPos: - h: 6 - w: 10 - x: 0 - 'y': 6 - id: 7 - legend: - avg: false - current: false - max: false - min: false - show: true - total: false - values: false - lines: true - linewidth: 1 - links: [] - nullPointMode: 'null' - options: - dataLinks: [] - percentage: false - pointradius: 5 - points: false - renderer: flot - seriesOverrides: [] - spaceLength: 10 - stack: false - steppedLine: false - targets: - - expr: >- - 'rate(mongodb_op_counters_total{instance=~"$instance"}[$interval])' - format: time_series - interval: '' - intervalFactor: 2 - legendFormat: '{{type}}' - refId: A - step: 240 - thresholds: [] - timeFrom: null - timeRegions: [] - timeShift: null - title: Query Operations - tooltip: - shared: true - sort: 0 - value_type: individual - type: graph - xaxis: - buckets: null - mode: time - name: null - show: true - values: [] - yaxes: - - format: ops - label: null - logBase: 1 - max: null - min: null - show: true - - format: short - label: null - logBase: 1 - max: null - min: null - show: true - yaxis: - align: false - alignLevel: null - - aliasColors: {} - bars: false - dashLength: 10 - dashes: false - datasource: Prometheus - fill: 1 - fillGradient: 0 - gridPos: - h: 6 - w: 8 - x: 10 - 'y': 6 - id: 9 - legend: - avg: false - current: false - max: false - min: false - show: true - total: false - values: false - lines: true - linewidth: 1 - links: [] - nullPointMode: 'null' - options: - dataLinks: [] - percentage: false - pointradius: 5 - points: false - renderer: flot - seriesOverrides: - - alias: returned - yaxis: 1 - spaceLength: 10 - stack: false - steppedLine: false - targets: - - expr: >- - rate(mongodb_mongod_metrics_document_total{instance=~"$instance"}[$interval]) - format: time_series - interval: '' - intervalFactor: 2 - legendFormat: '{{state}}' - refId: A - step: 240 - thresholds: [] - timeFrom: null - timeRegions: [] - timeShift: null - title: Document Operations - tooltip: - shared: true - sort: 0 - value_type: individual - type: graph - xaxis: - buckets: null - mode: time - name: null - show: true - values: [] - yaxes: - - format: short - label: null - logBase: 1 - max: null - min: null - show: true - - format: short - label: null - logBase: 1 - max: null - min: null - show: true - yaxis: - align: false - alignLevel: null - - aliasColors: {} - bars: false - dashLength: 10 - dashes: false - datasource: Prometheus - fill: 1 - fillGradient: 0 - gridPos: - h: 6 - w: 6 - x: 18 - 'y': 6 - id: 8 - legend: - avg: false - current: false - max: false - min: false - show: true - total: false - values: false - lines: true - linewidth: 1 - links: [] - nullPointMode: 'null' - options: - dataLinks: [] - percentage: false - pointradius: 5 - points: false - renderer: flot - seriesOverrides: [] - spaceLength: 10 - stack: false - steppedLine: false - targets: - - expr: >- - rate(mongodb_mongod_metrics_query_executor_total{instance=~"$instance"}[$interval]) - format: time_series - interval: '' - intervalFactor: 2 - legendFormat: '{{state}}' - refId: A - step: 600 - thresholds: [] - timeFrom: null - timeRegions: [] - timeShift: null - title: Document Query Executor - tooltip: - shared: true - sort: 0 - value_type: individual - type: graph - xaxis: - buckets: null - mode: time - name: null - show: true - values: [] - yaxes: - - format: short - label: null - logBase: 1 - max: null - min: null - show: true - - format: short - label: null - logBase: 1 - max: null - min: null - show: true - yaxis: - align: false - alignLevel: null - - collapsed: false - gridPos: - h: 1 - w: 24 - x: 0 - 'y': 12 - id: 23 - panels: [] - repeat: null - title: Resources - type: row - - aliasColors: {} - bars: false - dashLength: 10 - dashes: false - datasource: Prometheus - fill: 1 - fillGradient: 0 - gridPos: - h: 6 - w: 12 - x: 0 - 'y': 13 - id: 4 - legend: - alignAsTable: false - avg: false - current: true - hideEmpty: false - hideZero: false - max: false - min: false - rightSide: false - show: true - total: false - values: true - lines: true - linewidth: 1 - links: [] - nullPointMode: 'null' - options: - dataLinks: [] - percentage: false - pointradius: 5 - points: false - renderer: flot - seriesOverrides: [] - spaceLength: 10 - stack: false - steppedLine: false - targets: - - expr: 'mongodb_memory{instance=~"$instance",type=~"resident|virtual"}' - format: time_series - interval: '' - intervalFactor: 2 - legendFormat: '{{type}}' - refId: A - step: 240 - thresholds: [] - timeFrom: null - timeRegions: [] - timeShift: null - title: Memory - tooltip: - shared: false - sort: 0 - value_type: individual - type: graph - xaxis: - buckets: null - mode: time - name: null - show: true - values: - - total - yaxes: - - format: decmbytes - label: MB - logBase: 1 - max: null - min: null - show: true - - format: short - label: null - logBase: 1 - max: null - min: null - show: true - yaxis: - align: false - alignLevel: null - - aliasColors: {} - bars: false - dashLength: 10 - dashes: false - datasource: Prometheus - fill: 1 - fillGradient: 0 - gridPos: - h: 6 - w: 12 - x: 12 - 'y': 13 - id: 5 - legend: - avg: false - current: false - max: false - min: false - show: true - total: false - values: false - lines: true - linewidth: 1 - links: [] - nullPointMode: 'null' - options: - dataLinks: [] - percentage: false - pointradius: 5 - points: false - renderer: flot - seriesOverrides: [] - spaceLength: 10 - stack: false - steppedLine: false - targets: - - expr: >- - 'rate(mongodb_network_bytes_total{instance=~"$instance"}[$interval])' - format: time_series - interval: '' - intervalFactor: 2 - legendFormat: '{{state}}' - metric: mongodb_metrics_operation_total - refId: A - step: 240 - thresholds: [] - timeFrom: null - timeRegions: [] - timeShift: null - title: Network I/O - tooltip: - shared: true - sort: 0 - value_type: individual - type: graph - xaxis: - buckets: null - mode: time - name: null - show: true - values: [] - yaxes: - - format: decbytes - label: null - logBase: 1 - max: null - min: null - show: true - - format: short - label: null - logBase: 1 - max: null - min: null - show: true - yaxis: - align: false - alignLevel: null -refresh: 5s -schemaVersion: 19 -style: dark -tags: [] -templating: - list: - - allValue: null - current: - text: All - value: $__all - datasource: Prometheus - definition: '' - hide: 0 - includeAll: true - label: instance - multi: true - name: instance - options: [] - query: 'label_values(mongodb_connections, instance)' - refresh: 1 - regex: '' - skipUrlSync: false - sort: 1 - tagValuesQuery: /.*-(.*?)-.*/ - tags: [] - tagsQuery: 'label_values(mongodb_connections, instance)' - type: query - useTags: false - - auto: true - auto_count: 30 - auto_min: 10s - current: - text: auto - value: $__auto_interval_interval - hide: 0 - label: null - name: interval - options: - - selected: true - text: auto - value: $__auto_interval_interval - - selected: false - text: 1m - value: 1m - - selected: false - text: 10m - value: 10m - - selected: false - text: 30m - value: 30m - - selected: false - text: 1h - value: 1h - - selected: false - text: 6h - value: 6h - - selected: false - text: 12h - value: 12h - - selected: false - text: 1d - value: 1d - - selected: false - text: 7d - value: 7d - - selected: false - text: 14d - value: 14d - - selected: false - text: 30d - value: 30d - query: '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d' - refresh: 2 - skipUrlSync: false - type: interval -time: - from: now/d - to: now -timepicker: - refresh_intervals: - - 5s - - 10s - - 30s - - 1m - - 5m - - 15m - - 30m - - 1h - - 2h - - 1d - time_options: - - 5m - - 15m - - 1h - - 6h - - 12h - - 24h - - 2d - - 7d - - 30d -timezone: browser -title: MongoDB -uid: HEK4NbtZk -version: 2 diff --git a/installers/charm/prometheus-mongodb-exporter/metadata.yaml b/installers/charm/prometheus-mongodb-exporter/metadata.yaml deleted file mode 100644 index 78c4a311..00000000 --- a/installers/charm/prometheus-mongodb-exporter/metadata.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -name: prometheus-mongodb-exporter -summary: OSM Prometheus Mongodb Exporter -description: | - A CAAS charm to deploy OSM's Prometheus Mongodb Exporter. -series: - - kubernetes -tags: - - kubernetes - - osm - - prometheus - - mongodb-exporter -min-juju-version: 2.8.0 -deployment: - type: stateless - service: cluster -resources: - image: - type: oci-image - description: Image of mongodb-exporter - upstream-source: "bitnami/mongodb-exporter:latest" -provides: - prometheus-target: - interface: http - grafana-dashboard: - interface: grafana-dashboard -requires: - mongodb: - interface: mongodb diff --git a/installers/charm/prometheus-mongodb-exporter/requirements.txt b/installers/charm/prometheus-mongodb-exporter/requirements.txt deleted file mode 100644 index 884cf9f9..00000000 --- a/installers/charm/prometheus-mongodb-exporter/requirements.txt +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -ops -git+https://github.com/juju-solutions/resource-oci-image/@c5778285d332edf3d9a538f9d0c06154b7ec1b0b#egg=oci-image diff --git a/installers/charm/prometheus-mongodb-exporter/src/charm.py b/installers/charm/prometheus-mongodb-exporter/src/charm.py deleted file mode 100755 index 12b3c1c2..00000000 --- a/installers/charm/prometheus-mongodb-exporter/src/charm.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -import logging -from pathlib import Path -from typing import Dict, List, NoReturn - -from ops.charm import CharmBase -from ops.framework import EventBase, StoredState -from ops.main import main -from ops.model import ActiveStatus, Application, BlockedStatus, MaintenanceStatus, Unit -from oci_image import OCIImageResource, OCIImageResourceError - -from pod_spec import make_pod_spec - -logger = logging.getLogger(__name__) - -PROMETHEUS_MONGODB_EXPORTER_PORT = 9216 - - -class RelationsMissing(Exception): - def __init__(self, missing_relations: List): - self.message = "" - if missing_relations and isinstance(missing_relations, list): - self.message += f'Waiting for {", ".join(missing_relations)} relation' - if "," in self.message: - self.message += "s" - - -class RelationDefinition: - def __init__(self, relation_name: str, keys: List, source_type): - if source_type != Application and source_type != Unit: - raise TypeError( - "source_type should be ops.model.Application or ops.model.Unit" - ) - self.relation_name = relation_name - self.keys = keys - self.source_type = source_type - - -def check_missing_relation_data( - data: Dict, - expected_relations_data: List[RelationDefinition], -): - missing_relations = [] - for relation_data in expected_relations_data: - if not all( - f"{relation_data.relation_name}_{k}" in data for k in relation_data.keys - ): - missing_relations.append(relation_data.relation_name) - if missing_relations: - raise RelationsMissing(missing_relations) - - -def get_relation_data( - charm: CharmBase, - relation_data: RelationDefinition, -) -> Dict: - data = {} - relation = charm.model.get_relation(relation_data.relation_name) - if relation: - self_app_unit = ( - charm.app if relation_data.source_type == Application else charm.unit - ) - expected_type = relation_data.source_type - for app_unit in relation.data: - if app_unit != self_app_unit and isinstance(app_unit, expected_type): - if all(k in relation.data[app_unit] for k in relation_data.keys): - for k in relation_data.keys: - data[f"{relation_data.relation_name}_{k}"] = relation.data[ - app_unit - ].get(k) - break - return data - - -class PrometheusMongodbExporterCharm(CharmBase): - """Prometheus Mongodb Exporter Charm.""" - - state = StoredState() - - def __init__(self, *args) -> NoReturn: - """Prometheus Mongodb Exporter Charm constructor.""" - super().__init__(*args) - - # Internal state initialization - self.state.set_default(pod_spec=None) - - self.port = PROMETHEUS_MONGODB_EXPORTER_PORT - self.image = OCIImageResource(self, "image") - - # Registering regular events - self.framework.observe(self.on.start, self.configure_pod) - self.framework.observe(self.on.config_changed, self.configure_pod) - - # Registering required relation events - self.framework.observe(self.on.mongodb_relation_changed, self.configure_pod) - - # Registering required relation departed events - self.framework.observe(self.on.mongodb_relation_departed, self.configure_pod) - - # Registering provided relation events - self.framework.observe( - self.on.prometheus_target_relation_joined, self._publish_target_info - ) - self.framework.observe( - self.on.grafana_dashboard_relation_joined, self._publish_dashboard_info - ) - - def _publish_target_info(self, event: EventBase) -> NoReturn: - """Publishes target information. - - Args: - event (EventBase): Exporter relation event. - """ - rel_data = { - "hostname": self.model.app.name, - "port": str(PROMETHEUS_MONGODB_EXPORTER_PORT), - } - for k, v in rel_data.items(): - event.relation.data[self.unit][k] = v - - def _publish_dashboard_info(self, event: EventBase) -> NoReturn: - """Publishes dashboard information. - - Args: - event (EventBase): Exporter relation event. - """ - rel_data = { - "dashboard": Path("files/mongodb_exporter_dashboard.yaml").read_text(), - } - for k, v in rel_data.items(): - event.relation.data[self.unit][k] = v - - @property - def relations_requirements(self): - return [RelationDefinition("mongodb", ["connection_string"], Unit)] - - def get_relation_state(self): - relation_state = {} - for relation_requirements in self.relations_requirements: - data = get_relation_data(self, relation_requirements) - relation_state = {**relation_state, **data} - check_missing_relation_data(relation_state, self.relations_requirements) - return relation_state - - def configure_pod(self, _=None) -> NoReturn: - """Assemble the pod spec and apply it, if possible. - - Args: - event (EventBase): Hook or Relation event that started the - function. - """ - if not self.unit.is_leader(): - self.unit.status = ActiveStatus("ready") - return - - relation_state = None - try: - relation_state = self.get_relation_state() - except RelationsMissing as exc: - logger.exception("Relation missing error") - self.unit.status = BlockedStatus(exc.message) - return - - self.unit.status = MaintenanceStatus("Assembling pod spec") - - # Fetch image information - try: - self.unit.status = MaintenanceStatus("Fetching image information") - image_info = self.image.fetch() - except OCIImageResourceError: - self.unit.status = BlockedStatus("Error fetching image information") - return - - try: - pod_spec = make_pod_spec( - image_info, - self.model.config, - relation_state, - self.model.app.name, - self.port, - ) - except ValueError as exc: - logger.exception("Config/Relation data validation error") - self.unit.status = BlockedStatus(str(exc)) - return - - if self.state.pod_spec != pod_spec: - self.model.pod.set_spec(pod_spec) - self.state.pod_spec = pod_spec - - self.unit.status = ActiveStatus("ready") - - -if __name__ == "__main__": - main(PrometheusMongodbExporterCharm) diff --git a/installers/charm/prometheus-mongodb-exporter/src/pod_spec.py b/installers/charm/prometheus-mongodb-exporter/src/pod_spec.py deleted file mode 100644 index 44acb765..00000000 --- a/installers/charm/prometheus-mongodb-exporter/src/pod_spec.py +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -import logging -from ipaddress import ip_network -from typing import Any, Dict, List -from urllib.parse import urlparse - -logger = logging.getLogger(__name__) - - -def _validate_ip_network(network: str) -> bool: - """Validate IP network. - - Args: - network (str): IP network range. - - Returns: - bool: True if valid, false otherwise. - """ - if not network: - return True - - try: - ip_network(network) - except ValueError: - return False - - return True - - -def _validate_data(config_data: Dict[str, Any], relation_data: Dict[str, Any]) -> bool: - """Validates passed information. - - Args: - config_data (Dict[str, Any]): configuration information. - relation_data (Dict[str, Any]): relation information - - Raises: - ValueError: when config and/or relation data is not valid. - """ - config_validators = { - "site_url": lambda value, _: isinstance(value, str) - if value is not None - else True, - "ingress_whitelist_source_range": lambda value, _: _validate_ip_network(value), - "tls_secret_name": lambda value, _: isinstance(value, str) - if value is not None - else True, - } - relation_validators = { - "mongodb_connection_string": lambda value, _: ( - isinstance(value, str) and value.startswith("mongodb://") - ) - } - problems = [] - - for key, validator in config_validators.items(): - valid = validator(config_data.get(key), config_data) - - if not valid: - problems.append(key) - - for key, validator in relation_validators.items(): - valid = validator(relation_data.get(key), relation_data) - - if not valid: - problems.append(key) - - if len(problems) > 0: - raise ValueError("Errors found in: {}".format(", ".join(problems))) - - return True - - -def _make_pod_ports(port: int) -> List[Dict[str, Any]]: - """Generate pod ports details. - - Args: - port (int): port to expose. - - Returns: - List[Dict[str, Any]]: pod port details. - """ - return [ - { - "name": "prometheus-mongodb-exporter", - "containerPort": port, - "protocol": "TCP", - } - ] - - -def _make_pod_envconfig( - config: Dict[str, Any], relation_state: Dict[str, Any] -) -> Dict[str, Any]: - """Generate pod environment configuration. - - Args: - config (Dict[str, Any]): configuration information. - relation_state (Dict[str, Any]): relation state information. - - Returns: - Dict[str, Any]: pod environment configuration. - """ - envconfig = {} - - return envconfig - - -def _make_pod_ingress_resources( - config: Dict[str, Any], app_name: str, port: int -) -> List[Dict[str, Any]]: - """Generate pod ingress resources. - - Args: - config (Dict[str, Any]): configuration information. - app_name (str): application name. - port (int): port to expose. - - Returns: - List[Dict[str, Any]]: pod ingress resources. - """ - site_url = config.get("site_url") - - if not site_url: - return - - parsed = urlparse(site_url) - - if not parsed.scheme.startswith("http"): - return - - ingress_whitelist_source_range = config["ingress_whitelist_source_range"] - annotations = {} - - if ingress_whitelist_source_range: - annotations[ - "nginx.ingress.kubernetes.io/whitelist-source-range" - ] = ingress_whitelist_source_range - - ingress_spec_tls = None - - if parsed.scheme == "https": - ingress_spec_tls = [{"hosts": [parsed.hostname]}] - tls_secret_name = config["tls_secret_name"] - if tls_secret_name: - ingress_spec_tls[0]["secretName"] = tls_secret_name - else: - annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false" - - ingress = { - "name": "{}-ingress".format(app_name), - "annotations": annotations, - "spec": { - "rules": [ - { - "host": parsed.hostname, - "http": { - "paths": [ - { - "path": "/", - "backend": { - "serviceName": app_name, - "servicePort": port, - }, - } - ] - }, - } - ] - }, - } - if ingress_spec_tls: - ingress["spec"]["tls"] = ingress_spec_tls - - return [ingress] - - -def _make_readiness_probe(port: int) -> Dict[str, Any]: - """Generate readiness probe. - - Args: - port (int): service port. - - Returns: - Dict[str, Any]: readiness probe. - """ - return { - "httpGet": { - "path": "/api/health", - "port": port, - }, - "initialDelaySeconds": 10, - "periodSeconds": 10, - "timeoutSeconds": 5, - "successThreshold": 1, - "failureThreshold": 3, - } - - -def _make_liveness_probe(port: int) -> Dict[str, Any]: - """Generate liveness probe. - - Args: - port (int): service port. - - Returns: - Dict[str, Any]: liveness probe. - """ - return { - "httpGet": { - "path": "/api/health", - "port": port, - }, - "initialDelaySeconds": 60, - "timeoutSeconds": 30, - "failureThreshold": 10, - } - - -def _make_pod_command(relation: Dict[str, Any]) -> List[str]: - """Generate the startup command. - - Args: - relation (Dict[str, Any]): Relation information. - - Returns: - List[str]: command to startup the process. - """ - command = [ - "mongodb_exporter_linux_amd64/mongodb_exporter", - "--mongodbdsn={}".format(relation.get("mongodb_connection_string")), - ] - - return command - - -def make_pod_spec( - image_info: Dict[str, str], - config: Dict[str, Any], - relation_state: Dict[str, Any], - app_name: str = "prometheus-mongodb-exporter", - port: int = 9216, -) -> Dict[str, Any]: - """Generate the pod spec information. - - Args: - image_info (Dict[str, str]): Object provided by - OCIImageResource("image").fetch(). - config (Dict[str, Any]): Configuration information. - relation_state (Dict[str, Any]): Relation state information. - app_name (str, optional): Application name. Defaults to "ro". - port (int, optional): Port for the container. Defaults to 9090. - - Returns: - Dict[str, Any]: Pod spec dictionary for the charm. - """ - if not image_info: - return None - - _validate_data(config, relation_state) - - ports = _make_pod_ports(port) - env_config = _make_pod_envconfig(config, relation_state) - readiness_probe = _make_readiness_probe(port) - liveness_probe = _make_liveness_probe(port) - ingress_resources = _make_pod_ingress_resources(config, app_name, port) - command = _make_pod_command(relation_state) - - return { - "version": 3, - "containers": [ - { - "name": app_name, - "imageDetails": image_info, - "imagePullPolicy": "Always", - "ports": ports, - "envConfig": env_config, - "command": command, - "kubernetes": { - "readinessProbe": readiness_probe, - "livenessProbe": liveness_probe, - }, - } - ], - "kubernetesResources": { - "ingressResources": ingress_resources or [], - }, - } diff --git a/installers/charm/prometheus-mongodb-exporter/tests/__init__.py b/installers/charm/prometheus-mongodb-exporter/tests/__init__.py deleted file mode 100644 index 4fd849a5..00000000 --- a/installers/charm/prometheus-mongodb-exporter/tests/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -"""Init mocking for unit tests.""" - -import sys -import mock - -sys.path.append("src") - -oci_image = mock.MagicMock() -sys.modules["oci_image"] = oci_image diff --git a/installers/charm/prometheus-mongodb-exporter/tests/test_charm.py b/installers/charm/prometheus-mongodb-exporter/tests/test_charm.py deleted file mode 100644 index 89d30cd9..00000000 --- a/installers/charm/prometheus-mongodb-exporter/tests/test_charm.py +++ /dev/null @@ -1,477 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -from typing import NoReturn -import unittest - -from ops.model import BlockedStatus -from ops.testing import Harness - -from charm import PrometheusMongodbExporterCharm - - -class TestCharm(unittest.TestCase): - """Prometheus Mongodb Exporter Charm unit tests.""" - - def setUp(self) -> NoReturn: - """Test setup""" - self.harness = Harness(PrometheusMongodbExporterCharm) - self.harness.set_leader(is_leader=True) - self.harness.begin() - - def test_on_start_without_relations(self) -> NoReturn: - """Test installation without any relation.""" - self.harness.charm.on.start.emit() - - # Verifying status - self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) - - # Verifying status message - self.assertGreater(len(self.harness.charm.unit.status.message), 0) - self.assertTrue( - self.harness.charm.unit.status.message.startswith("Waiting for ") - ) - self.assertIn("mongodb", self.harness.charm.unit.status.message) - self.assertTrue(self.harness.charm.unit.status.message.endswith(" relation")) - - def test_on_start_with_relations_without_http(self) -> NoReturn: - """Test deployment.""" - expected_result = { - "version": 3, - "containers": [ - { - "name": "prometheus-mongodb-exporter", - "imageDetails": self.harness.charm.image.fetch(), - "imagePullPolicy": "Always", - "ports": [ - { - "name": "prometheus-mongodb-exporter", - "containerPort": 9216, - "protocol": "TCP", - } - ], - "envConfig": {}, - "command": [ - "mongodb_exporter_linux_amd64/mongodb_exporter", - "--mongodbdsn=mongodb://mongo", - ], - "kubernetes": { - "readinessProbe": { - "httpGet": { - "path": "/api/health", - "port": 9216, - }, - "initialDelaySeconds": 10, - "periodSeconds": 10, - "timeoutSeconds": 5, - "successThreshold": 1, - "failureThreshold": 3, - }, - "livenessProbe": { - "httpGet": { - "path": "/api/health", - "port": 9216, - }, - "initialDelaySeconds": 60, - "timeoutSeconds": 30, - "failureThreshold": 10, - }, - }, - }, - ], - "kubernetesResources": {"ingressResources": []}, - } - - self.harness.charm.on.start.emit() - - # Initializing the mongodb relation - relation_id = self.harness.add_relation("mongodb", "mongodb") - self.harness.add_relation_unit(relation_id, "mongodb/0") - self.harness.update_relation_data( - relation_id, - "mongodb/0", - { - "connection_string": "mongodb://mongo", - }, - ) - - # Verifying status - self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) - - pod_spec, _ = self.harness.get_pod_spec() - - self.assertDictEqual(expected_result, pod_spec) - - def test_ingress_resources_with_http(self) -> NoReturn: - """Test ingress resources with HTTP.""" - expected_result = { - "version": 3, - "containers": [ - { - "name": "prometheus-mongodb-exporter", - "imageDetails": self.harness.charm.image.fetch(), - "imagePullPolicy": "Always", - "ports": [ - { - "name": "prometheus-mongodb-exporter", - "containerPort": 9216, - "protocol": "TCP", - } - ], - "envConfig": {}, - "command": [ - "mongodb_exporter_linux_amd64/mongodb_exporter", - "--mongodbdsn=mongodb://mongo", - ], - "kubernetes": { - "readinessProbe": { - "httpGet": { - "path": "/api/health", - "port": 9216, - }, - "initialDelaySeconds": 10, - "periodSeconds": 10, - "timeoutSeconds": 5, - "successThreshold": 1, - "failureThreshold": 3, - }, - "livenessProbe": { - "httpGet": { - "path": "/api/health", - "port": 9216, - }, - "initialDelaySeconds": 60, - "timeoutSeconds": 30, - "failureThreshold": 10, - }, - }, - }, - ], - "kubernetesResources": { - "ingressResources": [ - { - "name": "prometheus-mongodb-exporter-ingress", - "annotations": { - "nginx.ingress.kubernetes.io/ssl-redirect": "false", - }, - "spec": { - "rules": [ - { - "host": "prometheus-mongodb-exporter", - "http": { - "paths": [ - { - "path": "/", - "backend": { - "serviceName": "prometheus-mongodb-exporter", - "servicePort": 9216, - }, - } - ] - }, - } - ] - }, - } - ], - }, - } - - self.harness.charm.on.start.emit() - - # Initializing the mongodb relation - relation_id = self.harness.add_relation("mongodb", "mongodb") - self.harness.add_relation_unit(relation_id, "mongodb/0") - self.harness.update_relation_data( - relation_id, - "mongodb/0", - { - "connection_string": "mongodb://mongo", - }, - ) - - self.harness.update_config({"site_url": "http://prometheus-mongodb-exporter"}) - - pod_spec, _ = self.harness.get_pod_spec() - - self.assertDictEqual(expected_result, pod_spec) - - def test_ingress_resources_with_https(self) -> NoReturn: - """Test ingress resources with HTTPS.""" - expected_result = { - "version": 3, - "containers": [ - { - "name": "prometheus-mongodb-exporter", - "imageDetails": self.harness.charm.image.fetch(), - "imagePullPolicy": "Always", - "ports": [ - { - "name": "prometheus-mongodb-exporter", - "containerPort": 9216, - "protocol": "TCP", - } - ], - "envConfig": {}, - "command": [ - "mongodb_exporter_linux_amd64/mongodb_exporter", - "--mongodbdsn=mongodb://mongo", - ], - "kubernetes": { - "readinessProbe": { - "httpGet": { - "path": "/api/health", - "port": 9216, - }, - "initialDelaySeconds": 10, - "periodSeconds": 10, - "timeoutSeconds": 5, - "successThreshold": 1, - "failureThreshold": 3, - }, - "livenessProbe": { - "httpGet": { - "path": "/api/health", - "port": 9216, - }, - "initialDelaySeconds": 60, - "timeoutSeconds": 30, - "failureThreshold": 10, - }, - }, - }, - ], - "kubernetesResources": { - "ingressResources": [ - { - "name": "prometheus-mongodb-exporter-ingress", - "annotations": {}, - "spec": { - "rules": [ - { - "host": "prometheus-mongodb-exporter", - "http": { - "paths": [ - { - "path": "/", - "backend": { - "serviceName": "prometheus-mongodb-exporter", - "servicePort": 9216, - }, - } - ] - }, - } - ], - "tls": [ - { - "hosts": ["prometheus-mongodb-exporter"], - "secretName": "prometheus-mongodb-exporter", - } - ], - }, - } - ], - }, - } - - self.harness.charm.on.start.emit() - - # Initializing the mongodb relation - relation_id = self.harness.add_relation("mongodb", "mongodb") - self.harness.add_relation_unit(relation_id, "mongodb/0") - self.harness.update_relation_data( - relation_id, - "mongodb/0", - { - "connection_string": "mongodb://mongo", - }, - ) - - self.harness.update_config( - { - "site_url": "https://prometheus-mongodb-exporter", - "tls_secret_name": "prometheus-mongodb-exporter", - } - ) - - pod_spec, _ = self.harness.get_pod_spec() - - self.assertDictEqual(expected_result, pod_spec) - - def test_ingress_resources_with_https_and_ingress_whitelist(self) -> NoReturn: - """Test ingress resources with HTTPS and ingress whitelist.""" - expected_result = { - "version": 3, - "containers": [ - { - "name": "prometheus-mongodb-exporter", - "imageDetails": self.harness.charm.image.fetch(), - "imagePullPolicy": "Always", - "ports": [ - { - "name": "prometheus-mongodb-exporter", - "containerPort": 9216, - "protocol": "TCP", - } - ], - "envConfig": {}, - "command": [ - "mongodb_exporter_linux_amd64/mongodb_exporter", - "--mongodbdsn=mongodb://mongo", - ], - "kubernetes": { - "readinessProbe": { - "httpGet": { - "path": "/api/health", - "port": 9216, - }, - "initialDelaySeconds": 10, - "periodSeconds": 10, - "timeoutSeconds": 5, - "successThreshold": 1, - "failureThreshold": 3, - }, - "livenessProbe": { - "httpGet": { - "path": "/api/health", - "port": 9216, - }, - "initialDelaySeconds": 60, - "timeoutSeconds": 30, - "failureThreshold": 10, - }, - }, - }, - ], - "kubernetesResources": { - "ingressResources": [ - { - "name": "prometheus-mongodb-exporter-ingress", - "annotations": { - "nginx.ingress.kubernetes.io/whitelist-source-range": "0.0.0.0/0", - }, - "spec": { - "rules": [ - { - "host": "prometheus-mongodb-exporter", - "http": { - "paths": [ - { - "path": "/", - "backend": { - "serviceName": "prometheus-mongodb-exporter", - "servicePort": 9216, - }, - } - ] - }, - } - ], - "tls": [ - { - "hosts": ["prometheus-mongodb-exporter"], - "secretName": "prometheus-mongodb-exporter", - } - ], - }, - } - ], - }, - } - - self.harness.charm.on.start.emit() - - # Initializing the mongodb relation - relation_id = self.harness.add_relation("mongodb", "mongodb") - self.harness.add_relation_unit(relation_id, "mongodb/0") - self.harness.update_relation_data( - relation_id, - "mongodb/0", - { - "connection_string": "mongodb://mongo", - }, - ) - - self.harness.update_config( - { - "site_url": "https://prometheus-mongodb-exporter", - "tls_secret_name": "prometheus-mongodb-exporter", - "ingress_whitelist_source_range": "0.0.0.0/0", - } - ) - - pod_spec, _ = self.harness.get_pod_spec() - - self.assertDictEqual(expected_result, pod_spec) - - def test_on_mongodb_unit_relation_changed(self) -> NoReturn: - """Test to see if mongodb relation is updated.""" - self.harness.charm.on.start.emit() - - # Initializing the mongodb relation - relation_id = self.harness.add_relation("mongodb", "mongodb") - self.harness.add_relation_unit(relation_id, "mongodb/0") - self.harness.update_relation_data( - relation_id, - "mongodb/0", - { - "connection_string": "mongodb://mongo", - }, - ) - - # Verifying status - self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) - - def test_publish_target_info(self) -> NoReturn: - """Test to see if target relation is updated.""" - expected_result = { - "hostname": "prometheus-mongodb-exporter", - "port": "9216", - } - - self.harness.charm.on.start.emit() - - relation_id = self.harness.add_relation("prometheus-target", "prometheus") - self.harness.add_relation_unit(relation_id, "prometheus/0") - relation_data = self.harness.get_relation_data( - relation_id, "prometheus-mongodb-exporter/0" - ) - - self.assertDictEqual(expected_result, relation_data) - - def test_publish_dashboard_info(self) -> NoReturn: - """Test to see if dashboard relation is updated.""" - self.harness.charm.on.start.emit() - - relation_id = self.harness.add_relation("grafana-dashboard", "grafana") - self.harness.add_relation_unit(relation_id, "grafana/0") - relation_data = self.harness.get_relation_data( - relation_id, "prometheus-mongodb-exporter/0" - ) - - self.assertTrue("dashboard" in relation_data) - self.assertTrue(len(relation_data["dashboard"]) > 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/installers/charm/prometheus-mongodb-exporter/tests/test_pod_spec.py b/installers/charm/prometheus-mongodb-exporter/tests/test_pod_spec.py deleted file mode 100644 index e907cc03..00000000 --- a/installers/charm/prometheus-mongodb-exporter/tests/test_pod_spec.py +++ /dev/null @@ -1,497 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -from typing import NoReturn -import unittest - -import pod_spec - - -class TestPodSpec(unittest.TestCase): - """Pod spec unit tests.""" - - def test_make_pod_ports(self) -> NoReturn: - """Testing make pod ports.""" - port = 9216 - - expected_result = [ - { - "name": "prometheus-mongodb-exporter", - "containerPort": port, - "protocol": "TCP", - } - ] - - pod_ports = pod_spec._make_pod_ports(port) - - self.assertListEqual(expected_result, pod_ports) - - def test_make_pod_envconfig(self) -> NoReturn: - """Teting make pod envconfig.""" - config = {} - relation_state = { - "mongodb_connection_string": "mongodb://mongo", - } - - expected_result = {} - - pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state) - - self.assertDictEqual(expected_result, pod_envconfig) - - def test_make_pod_ingress_resources_without_site_url(self) -> NoReturn: - """Testing make pod ingress resources without site_url.""" - config = {"site_url": ""} - app_name = "prometheus-mongodb-exporter" - port = 9216 - - pod_ingress_resources = pod_spec._make_pod_ingress_resources( - config, app_name, port - ) - - self.assertIsNone(pod_ingress_resources) - - def test_make_pod_ingress_resources(self) -> NoReturn: - """Testing make pod ingress resources.""" - config = { - "site_url": "http://prometheus-mongodb-exporter", - "ingress_whitelist_source_range": "", - } - app_name = "prometheus-mongodb-exporter" - port = 9216 - - expected_result = [ - { - "name": f"{app_name}-ingress", - "annotations": { - "nginx.ingress.kubernetes.io/ssl-redirect": "false", - }, - "spec": { - "rules": [ - { - "host": app_name, - "http": { - "paths": [ - { - "path": "/", - "backend": { - "serviceName": app_name, - "servicePort": port, - }, - } - ] - }, - } - ] - }, - } - ] - - pod_ingress_resources = pod_spec._make_pod_ingress_resources( - config, app_name, port - ) - - self.assertListEqual(expected_result, pod_ingress_resources) - - def test_make_pod_ingress_resources_with_whitelist_source_range(self) -> NoReturn: - """Testing make pod ingress resources with whitelist_source_range.""" - config = { - "site_url": "http://prometheus-mongodb-exporter", - "ingress_whitelist_source_range": "0.0.0.0/0", - } - app_name = "prometheus-mongodb-exporter" - port = 9216 - - expected_result = [ - { - "name": f"{app_name}-ingress", - "annotations": { - "nginx.ingress.kubernetes.io/ssl-redirect": "false", - "nginx.ingress.kubernetes.io/whitelist-source-range": config[ - "ingress_whitelist_source_range" - ], - }, - "spec": { - "rules": [ - { - "host": app_name, - "http": { - "paths": [ - { - "path": "/", - "backend": { - "serviceName": app_name, - "servicePort": port, - }, - } - ] - }, - } - ] - }, - } - ] - - pod_ingress_resources = pod_spec._make_pod_ingress_resources( - config, app_name, port - ) - - self.assertListEqual(expected_result, pod_ingress_resources) - - def test_make_pod_ingress_resources_with_https(self) -> NoReturn: - """Testing make pod ingress resources with HTTPs.""" - config = { - "site_url": "https://prometheus-mongodb-exporter", - "ingress_whitelist_source_range": "", - "tls_secret_name": "", - } - app_name = "prometheus-mongodb-exporter" - port = 9216 - - expected_result = [ - { - "name": f"{app_name}-ingress", - "annotations": {}, - "spec": { - "rules": [ - { - "host": app_name, - "http": { - "paths": [ - { - "path": "/", - "backend": { - "serviceName": app_name, - "servicePort": port, - }, - } - ] - }, - } - ], - "tls": [{"hosts": [app_name]}], - }, - } - ] - - pod_ingress_resources = pod_spec._make_pod_ingress_resources( - config, app_name, port - ) - - self.assertListEqual(expected_result, pod_ingress_resources) - - def test_make_pod_ingress_resources_with_https_tls_secret_name(self) -> NoReturn: - """Testing make pod ingress resources with HTTPs and TLS secret name.""" - config = { - "site_url": "https://prometheus-mongodb-exporter", - "ingress_whitelist_source_range": "", - "tls_secret_name": "secret_name", - } - app_name = "prometheus-mongodb-exporter" - port = 9216 - - expected_result = [ - { - "name": f"{app_name}-ingress", - "annotations": {}, - "spec": { - "rules": [ - { - "host": app_name, - "http": { - "paths": [ - { - "path": "/", - "backend": { - "serviceName": app_name, - "servicePort": port, - }, - } - ] - }, - } - ], - "tls": [ - {"hosts": [app_name], "secretName": config["tls_secret_name"]} - ], - }, - } - ] - - pod_ingress_resources = pod_spec._make_pod_ingress_resources( - config, app_name, port - ) - - self.assertListEqual(expected_result, pod_ingress_resources) - - def test_make_readiness_probe(self) -> NoReturn: - """Testing make readiness probe.""" - port = 9216 - - expected_result = { - "httpGet": { - "path": "/api/health", - "port": port, - }, - "initialDelaySeconds": 10, - "periodSeconds": 10, - "timeoutSeconds": 5, - "successThreshold": 1, - "failureThreshold": 3, - } - - readiness_probe = pod_spec._make_readiness_probe(port) - - self.assertDictEqual(expected_result, readiness_probe) - - def test_make_liveness_probe(self) -> NoReturn: - """Testing make liveness probe.""" - port = 9216 - - expected_result = { - "httpGet": { - "path": "/api/health", - "port": port, - }, - "initialDelaySeconds": 60, - "timeoutSeconds": 30, - "failureThreshold": 10, - } - - liveness_probe = pod_spec._make_liveness_probe(port) - - self.assertDictEqual(expected_result, liveness_probe) - - def test_make_pod_command(self) -> NoReturn: - """Testing make pod command.""" - relation = { - "mongodb_connection_string": "mongodb://mongo", - } - - expected_result = [ - "mongodb_exporter_linux_amd64/mongodb_exporter", - "--mongodbdsn={}".format(relation.get("mongodb_connection_string")), - ] - - pod_envconfig = pod_spec._make_pod_command(relation) - - self.assertListEqual(expected_result, pod_envconfig) - - def test_make_pod_spec(self) -> NoReturn: - """Testing make pod spec.""" - image_info = {"upstream-source": "bitnami/mongodb-exporter:latest"} - config = { - "site_url": "", - } - relation_state = { - "mongodb_connection_string": "mongodb://mongo", - } - app_name = "prometheus-mongodb-exporter" - port = 9216 - - expected_result = { - "version": 3, - "containers": [ - { - "name": app_name, - "imageDetails": image_info, - "imagePullPolicy": "Always", - "ports": [ - { - "name": app_name, - "containerPort": port, - "protocol": "TCP", - } - ], - "envConfig": {}, - "command": [ - "mongodb_exporter_linux_amd64/mongodb_exporter", - "--mongodbdsn=mongodb://mongo", - ], - "kubernetes": { - "readinessProbe": { - "httpGet": { - "path": "/api/health", - "port": port, - }, - "initialDelaySeconds": 10, - "periodSeconds": 10, - "timeoutSeconds": 5, - "successThreshold": 1, - "failureThreshold": 3, - }, - "livenessProbe": { - "httpGet": { - "path": "/api/health", - "port": port, - }, - "initialDelaySeconds": 60, - "timeoutSeconds": 30, - "failureThreshold": 10, - }, - }, - } - ], - "kubernetesResources": {"ingressResources": []}, - } - - spec = pod_spec.make_pod_spec( - image_info, config, relation_state, app_name, port - ) - - self.assertDictEqual(expected_result, spec) - - def test_make_pod_spec_with_ingress(self) -> NoReturn: - """Testing make pod spec.""" - image_info = {"upstream-source": "bitnami/mongodb-exporter:latest"} - config = { - "site_url": "https://prometheus-mongodb-exporter", - "tls_secret_name": "prometheus-mongodb-exporter", - "ingress_whitelist_source_range": "0.0.0.0/0", - } - relation_state = { - "mongodb_connection_string": "mongodb://mongo", - } - app_name = "prometheus-mongodb-exporter" - port = 9216 - - expected_result = { - "version": 3, - "containers": [ - { - "name": app_name, - "imageDetails": image_info, - "imagePullPolicy": "Always", - "ports": [ - { - "name": app_name, - "containerPort": port, - "protocol": "TCP", - } - ], - "envConfig": {}, - "command": [ - "mongodb_exporter_linux_amd64/mongodb_exporter", - "--mongodbdsn=mongodb://mongo", - ], - "kubernetes": { - "readinessProbe": { - "httpGet": { - "path": "/api/health", - "port": port, - }, - "initialDelaySeconds": 10, - "periodSeconds": 10, - "timeoutSeconds": 5, - "successThreshold": 1, - "failureThreshold": 3, - }, - "livenessProbe": { - "httpGet": { - "path": "/api/health", - "port": port, - }, - "initialDelaySeconds": 60, - "timeoutSeconds": 30, - "failureThreshold": 10, - }, - }, - } - ], - "kubernetesResources": { - "ingressResources": [ - { - "name": "{}-ingress".format(app_name), - "annotations": { - "nginx.ingress.kubernetes.io/whitelist-source-range": config.get( - "ingress_whitelist_source_range" - ), - }, - "spec": { - "rules": [ - { - "host": app_name, - "http": { - "paths": [ - { - "path": "/", - "backend": { - "serviceName": app_name, - "servicePort": port, - }, - } - ] - }, - } - ], - "tls": [ - { - "hosts": [app_name], - "secretName": config.get("tls_secret_name"), - } - ], - }, - } - ], - }, - } - - spec = pod_spec.make_pod_spec( - image_info, config, relation_state, app_name, port - ) - - self.assertDictEqual(expected_result, spec) - - def test_make_pod_spec_without_image_info(self) -> NoReturn: - """Testing make pod spec without image_info.""" - image_info = None - config = { - "site_url": "", - } - relation_state = { - "mongodb_connection_string": "mongodb://mongo", - } - app_name = "prometheus-mongodb-exporter" - port = 9216 - - spec = pod_spec.make_pod_spec( - image_info, config, relation_state, app_name, port - ) - - self.assertIsNone(spec) - - def test_make_pod_spec_without_relation_state(self) -> NoReturn: - """Testing make pod spec without relation_state.""" - image_info = {"upstream-source": "bitnami/mongodb-exporter:latest"} - config = { - "site_url": "", - } - relation_state = {} - app_name = "prometheus-mongodb-exporter" - port = 9216 - - with self.assertRaises(ValueError): - pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port) - - -if __name__ == "__main__": - unittest.main() diff --git a/installers/charm/prometheus-mongodb-exporter/tox.ini b/installers/charm/prometheus-mongodb-exporter/tox.ini deleted file mode 100644 index a20a6f0a..00000000 --- a/installers/charm/prometheus-mongodb-exporter/tox.ini +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact: legal@canonical.com -# -# To get in touch with the maintainers, please contact: -# osm-charmers@lists.launchpad.net -## - -[tox] -skipsdist = True -envlist = unit, lint -sitepackages = False -skip_missing_interpreters = False - -[testenv] -basepython = python3 -setenv = - PYTHONHASHSEED=0 - PYTHONPATH = {toxinidir}/src - CHARM_NAME = prometheus-mongodb-exporter - -[testenv:build] -passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY -whitelist_externals = - charmcraft - rm - unzip -commands = - rm -rf release prometheus-mongodb-exporter.charm - charmcraft build - unzip prometheus-mongodb-exporter.charm -d release - -[testenv:unit] -commands = - coverage erase - stestr run --slowest --test-path=./tests --top-dir=./ - coverage combine - coverage html -d cover - coverage xml -o cover/coverage.xml - coverage report -deps = - coverage - stestr - mock - ops -setenv = - {[testenv]setenv} - PYTHON=coverage run - -[testenv:lint] -deps = - black - yamllint - flake8 -commands = - black --check --diff . --exclude "build/|.tox/|mod/|lib/" - yamllint . - flake8 . --max-line-length=100 --ignore="E501,W503,W504,F722" --exclude "build/ .tox/ mod/ lib/" - -[coverage:run] -branch = True -concurrency = multiprocessing -parallel = True -source = - . -omit = - .tox/* - tests/*