Major improvement in OSM charms
- Adapt all new operator charms to use the same pattern. They are all
using now this library that encapsulates the common logic for all
charms: https://github.com/davigar15/ops-lib-charmed-osm. That will be
eventually moved to gitlab, when it has a PyPI repository available
- Add unit tests to all charms
- Modify installer and bundles to point to the new charms
- Improve the build.sh script for building the charms
Change-Id: I0896ceb082d1b6a76b3560c07482a4135a220a3f
Signed-off-by: David Garcia <david.garcia@canonical.com>
diff --git a/installers/charm/prometheus/.gitignore b/installers/charm/prometheus/.gitignore
index bf04eb4..2885df2 100644
--- a/installers/charm/prometheus/.gitignore
+++ b/installers/charm/prometheus/.gitignore
@@ -22,7 +22,9 @@
venv
.vscode
build
-prometheus.charm
+*.charm
.coverage
+coverage.xml
.stestr
cover
+release
\ No newline at end of file
diff --git a/installers/charm/prometheus/.jujuignore b/installers/charm/prometheus/.jujuignore
new file mode 100644
index 0000000..bf04eb4
--- /dev/null
+++ b/installers/charm/prometheus/.jujuignore
@@ -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
+prometheus.charm
+.coverage
+.stestr
+cover
diff --git a/installers/charm/prometheus/.yamllint.yaml b/installers/charm/prometheus/.yamllint.yaml
index f3ecd3a..d71fb69 100644
--- a/installers/charm/prometheus/.yamllint.yaml
+++ b/installers/charm/prometheus/.yamllint.yaml
@@ -28,6 +28,7 @@
- ".yamllint"
ignore: |
.tox
+ cover/
build/
- mod/
- lib/
+ venv
+ release/
diff --git a/installers/charm/prometheus/config.yaml b/installers/charm/prometheus/config.yaml
index baa04cd..d953de3 100644
--- a/installers/charm/prometheus/config.yaml
+++ b/installers/charm/prometheus/config.yaml
@@ -20,11 +20,11 @@
##
options:
- web_subpath:
+ web-subpath:
description: Subpath for accessing Prometheus
type: string
default: /
- default_target:
+ default-target:
description: Default target to be added in Prometheus
type: string
default: ""
diff --git a/installers/charm/prometheus/metadata.yaml b/installers/charm/prometheus/metadata.yaml
index 960904b..f021418 100644
--- a/installers/charm/prometheus/metadata.yaml
+++ b/installers/charm/prometheus/metadata.yaml
@@ -41,3 +41,7 @@
provides:
prometheus:
interface: prometheus
+storage:
+ data:
+ type: filesystem
+ location: /prometheus
diff --git a/installers/charm/prometheus/requirements-test.txt b/installers/charm/prometheus/requirements-test.txt
new file mode 100644
index 0000000..d7585f3
--- /dev/null
+++ b/installers/charm/prometheus/requirements-test.txt
@@ -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
+##
+-r requirements.txt
+coverage
+stestr
+mock
+black
+yamllint
+flake8
+safety
+requests-mock
+asynctest
+nose2
\ No newline at end of file
diff --git a/installers/charm/prometheus/requirements.txt b/installers/charm/prometheus/requirements.txt
index 884cf9f..f10a199 100644
--- a/installers/charm/prometheus/requirements.txt
+++ b/installers/charm/prometheus/requirements.txt
@@ -19,5 +19,4 @@
# osm-charmers@lists.launchpad.net
##
-ops
-git+https://github.com/juju-solutions/resource-oci-image/@c5778285d332edf3d9a538f9d0c06154b7ec1b0b#egg=oci-image
+git+https://github.com/davigar15/ops-lib-charmed-osm/@e7f26cd29b322e175a23cadbe4546b7f2bbf111c
\ No newline at end of file
diff --git a/installers/charm/prometheus/src/charm.py b/installers/charm/prometheus/src/charm.py
index 4371d47..3d72cac 100755
--- a/installers/charm/prometheus/src/charm.py
+++ b/installers/charm/prometheus/src/charm.py
@@ -20,175 +20,181 @@
# osm-charmers@lists.launchpad.net
##
+# pylint: disable=E0213
+
import logging
-from typing import Dict, List, NoReturn
+from typing import Optional, NoReturn
+from ipaddress import ip_network
-from ops.charm import CharmBase
-from ops.framework import EventBase, StoredState
+from ops.framework import EventBase
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
+from opslib.osm.charm import CharmedOsmBase
+
+from opslib.osm.pod import (
+ IngressResourceV3Builder,
+ FilesV3Builder,
+ ContainerV3Builder,
+ PodSpecV3Builder,
+)
+
+
+from opslib.osm.validator import (
+ ModelValidator,
+ validator,
+)
+
+from opslib.osm.interfaces.prometheus import PrometheusServer
+from urllib.parse import urlparse
logger = logging.getLogger(__name__)
-PROMETHEUS_PORT = 9090
+PORT = 9090
-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 ConfigModel(ModelValidator):
+ web_subpath: str
+ default_target: str
+ max_file_size: int
+ site_url: Optional[str]
+ ingress_whitelist_source_range: Optional[str]
+ tls_secret_name: Optional[str]
+ enable_web_admin_api: bool
+
+ @validator("web_subpath")
+ def validate_web_subpath(cls, v):
+ if len(v) < 1:
+ raise ValueError("web-subpath must be a non-empty string")
+ return v
+
+ @validator("max_file_size")
+ def validate_max_file_size(cls, v):
+ if v < 0:
+ raise ValueError("value must be equal or greater than 0")
+ return v
+
+ @validator("site_url")
+ def validate_site_url(cls, v):
+ if v:
+ parsed = urlparse(v)
+ if not parsed.scheme.startswith("http"):
+ raise ValueError("value must start with http")
+ return v
+
+ @validator("ingress_whitelist_source_range")
+ def validate_ingress_whitelist_source_range(cls, v):
+ if v:
+ ip_network(v)
+ return v
-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
+class PrometheusCharm(CharmedOsmBase):
-
-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 PrometheusCharm(CharmBase):
"""Prometheus Charm."""
- state = StoredState()
-
def __init__(self, *args) -> NoReturn:
"""Prometheus Charm constructor."""
- super().__init__(*args)
-
- # Internal state initialization
- self.state.set_default(pod_spec=None)
-
- self.port = PROMETHEUS_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)
+ super().__init__(*args, oci_image="image")
# Registering provided relation events
+ self.prometheus = PrometheusServer(self, "prometheus")
self.framework.observe(
- self.on.prometheus_relation_joined, self._publish_prometheus_info
+ self.on.prometheus_relation_joined, # pylint: disable=E1101
+ self._publish_prometheus_info,
)
def _publish_prometheus_info(self, event: EventBase) -> NoReturn:
- """Publishes Prometheus information.
+ self.prometheus.publish_info(self.app.name, PORT)
- Args:
- event (EventBase): Prometheus relation event.
- """
- if self.unit.is_leader():
- rel_data = {
- "host": self.model.app.name,
- "port": str(PROMETHEUS_PORT),
+ def _build_files(self, config: ConfigModel):
+ files_builder = FilesV3Builder()
+ files_builder.add_file(
+ "prometheus.yml",
+ (
+ "global:\n"
+ " scrape_interval: 15s\n"
+ " evaluation_interval: 15s\n"
+ "alerting:\n"
+ " alertmanagers:\n"
+ " - static_configs:\n"
+ " - targets:\n"
+ "rule_files:\n"
+ "scrape_configs:\n"
+ " - job_name: 'prometheus'\n"
+ " static_configs:\n"
+ f" - targets: [{config.default_target}]\n"
+ ),
+ )
+ return files_builder.build()
+
+ def build_pod_spec(self, image_info):
+ # Validate config
+ config = ConfigModel(**dict(self.config))
+ # Create Builder for the PodSpec
+ pod_spec_builder = PodSpecV3Builder()
+ # Build Container
+ container_builder = ContainerV3Builder(self.app.name, image_info)
+ container_builder.add_port(name=self.app.name, port=PORT)
+ container_builder.add_http_readiness_probe(
+ "/-/ready",
+ PORT,
+ initial_delay_seconds=10,
+ timeout_seconds=30,
+ )
+ container_builder.add_http_liveness_probe(
+ "/-/healthy",
+ PORT,
+ initial_delay_seconds=30,
+ period_seconds=30,
+ )
+ command = [
+ "/bin/prometheus",
+ "--config.file=/etc/prometheus/prometheus.yml",
+ "--storage.tsdb.path=/prometheus",
+ "--web.console.libraries=/usr/share/prometheus/console_libraries",
+ "--web.console.templates=/usr/share/prometheus/consoles",
+ f"--web.route-prefix={config.web_subpath}",
+ f"--web.external-url=http://localhost:{PORT}{config.web_subpath}",
+ ]
+ if config.enable_web_admin_api:
+ command.append("--web.enable-admin-api")
+ container_builder.add_command(command)
+ container_builder.add_volume_config(
+ "config", "/etc/prometheus", self._build_files(config)
+ )
+ container = container_builder.build()
+ # Add container to pod spec
+ pod_spec_builder.add_container(container)
+ # Add ingress resources to pod spec if site url exists
+ if config.site_url:
+ parsed = urlparse(config.site_url)
+ annotations = {
+ "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
+ str(config.max_file_size) + "m"
+ if config.max_file_size > 0
+ else config.max_file_size
+ ),
}
- for k, v in rel_data.items():
- event.relation.data[self.app][k] = v
-
- @property
- def relations_requirements(self):
- return []
-
- 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,
+ ingress_resource_builder = IngressResourceV3Builder(
+ f"{self.app.name}-ingress", annotations
)
- 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
+ if config.ingress_whitelist_source_range:
+ annotations[
+ "nginx.ingress.kubernetes.io/whitelist-source-range"
+ ] = config.ingress_whitelist_source_range
- self.unit.status = ActiveStatus("ready")
+ if parsed.scheme == "https":
+ ingress_resource_builder.add_tls(
+ [parsed.hostname], config.tls_secret_name
+ )
+ else:
+ annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
+
+ ingress_resource_builder.add_rule(parsed.hostname, self.app.name, PORT)
+ ingress_resource = ingress_resource_builder.build()
+ pod_spec_builder.add_ingress_resource(ingress_resource)
+ return pod_spec_builder.build()
if __name__ == "__main__":
diff --git a/installers/charm/prometheus/tests/test_charm.py b/installers/charm/prometheus/tests/test_charm.py
index 87d7bc5..b1848bd 100644
--- a/installers/charm/prometheus/tests/test_charm.py
+++ b/installers/charm/prometheus/tests/test_charm.py
@@ -20,10 +20,10 @@
# osm-charmers@lists.launchpad.net
##
+import sys
from typing import NoReturn
import unittest
-
-from ops.model import BlockedStatus
+from ops.model import ActiveStatus
from ops.testing import Harness
from charm import PrometheusCharm
@@ -34,445 +34,63 @@
def setUp(self) -> NoReturn:
"""Test setup"""
+ self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
self.harness = Harness(PrometheusCharm)
self.harness.set_leader(is_leader=True)
self.harness.begin()
+ self.config = {
+ "web-subpath": "/",
+ "default-target": "",
+ "max_file_size": 0,
+ "ingress_whitelist_source_range": "",
+ "tls_secret_name": "",
+ "site_url": "https://prometheus.192.168.100.100.xip.io",
+ "enable_web_admin_api": False,
+ }
+ self.harness.update_config(self.config)
- def test_ingress_resources_without_http(self) -> NoReturn:
+ def test_config_changed(
+ self,
+ ) -> NoReturn:
"""Test ingress resources without HTTP."""
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": "prometheus",
- "imageDetails": self.harness.charm.image.fetch(),
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": "prometheus",
- "containerPort": 9090,
- "protocol": "TCP",
- }
- ],
- "envConfig": {},
- "volumeConfig": [
- {
- "name": "config",
- "mountPath": "/etc/prometheus",
- "files": [
- {
- "path": "prometheus.yml",
- "content": (
- "global:\n"
- " scrape_interval: 15s\n"
- " evaluation_interval: 15s\n"
- "alerting:\n"
- " alertmanagers:\n"
- " - static_configs:\n"
- " - targets:\n"
- "rule_files:\n"
- "scrape_configs:\n"
- " - job_name: 'prometheus'\n"
- " static_configs:\n"
- " - targets: [{}]\n".format("")
- ),
- }
- ],
- }
- ],
- "command": [
- "/bin/prometheus",
- "--config.file=/etc/prometheus/prometheus.yml",
- "--storage.tsdb.path=/prometheus",
- "--web.console.libraries=/usr/share/prometheus/console_libraries",
- "--web.console.templates=/usr/share/prometheus/consoles",
- "--web.route-prefix={}".format("/"),
- "--web.external-url=http://localhost:{}{}".format(9090, "/"),
- ],
- "kubernetes": {
- "readinessProbe": {
- "httpGet": {
- "path": "/-/ready",
- "port": 9090,
- },
- "initialDelaySeconds": 10,
- "timeoutSeconds": 30,
- },
- "livenessProbe": {
- "httpGet": {
- "path": "/-/healthy",
- "port": 9090,
- },
- "initialDelaySeconds": 30,
- "periodSeconds": 30,
- },
- },
- }
- ],
- "kubernetesResources": {"ingressResources": []},
- }
- self.harness.charm.on.start.emit()
+ self.harness.charm.on.config_changed.emit()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
+ # Assertions
+ self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
- pod_spec, _ = self.harness.get_pod_spec()
+ def test_config_changed_non_leader(
+ self,
+ ) -> NoReturn:
+ """Test ingress resources without HTTP."""
+ self.harness.set_leader(is_leader=False)
+ self.harness.charm.on.config_changed.emit()
- self.assertDictEqual(expected_result, pod_spec)
+ # Assertions
+ self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
- def test_ingress_resources_with_http(self) -> NoReturn:
- """Test ingress resources with HTTP."""
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": "prometheus",
- "imageDetails": self.harness.charm.image.fetch(),
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": "prometheus",
- "containerPort": 9090,
- "protocol": "TCP",
- }
- ],
- "envConfig": {},
- "volumeConfig": [
- {
- "name": "config",
- "mountPath": "/etc/prometheus",
- "files": [
- {
- "path": "prometheus.yml",
- "content": (
- "global:\n"
- " scrape_interval: 15s\n"
- " evaluation_interval: 15s\n"
- "alerting:\n"
- " alertmanagers:\n"
- " - static_configs:\n"
- " - targets:\n"
- "rule_files:\n"
- "scrape_configs:\n"
- " - job_name: 'prometheus'\n"
- " static_configs:\n"
- " - targets: [{}]\n".format("")
- ),
- }
- ],
- }
- ],
- "command": [
- "/bin/prometheus",
- "--config.file=/etc/prometheus/prometheus.yml",
- "--storage.tsdb.path=/prometheus",
- "--web.console.libraries=/usr/share/prometheus/console_libraries",
- "--web.console.templates=/usr/share/prometheus/consoles",
- "--web.route-prefix={}".format("/"),
- "--web.external-url=http://localhost:{}{}".format(9090, "/"),
- ],
- "kubernetes": {
- "readinessProbe": {
- "httpGet": {
- "path": "/-/ready",
- "port": 9090,
- },
- "initialDelaySeconds": 10,
- "timeoutSeconds": 30,
- },
- "livenessProbe": {
- "httpGet": {
- "path": "/-/healthy",
- "port": 9090,
- },
- "initialDelaySeconds": 30,
- "periodSeconds": 30,
- },
- },
- }
- ],
- "kubernetesResources": {
- "ingressResources": [
- {
- "name": "prometheus-ingress",
- "annotations": {
- "nginx.ingress.kubernetes.io/proxy-body-size": "0",
- "nginx.ingress.kubernetes.io/ssl-redirect": "false",
- },
- "spec": {
- "rules": [
- {
- "host": "prometheus",
- "http": {
- "paths": [
- {
- "path": "/",
- "backend": {
- "serviceName": "prometheus",
- "servicePort": 9090,
- },
- }
- ]
- },
- }
- ]
- },
- }
- ],
- },
- }
-
- self.harness.charm.on.start.emit()
-
- self.harness.update_config({"site_url": "http://prometheus"})
-
- 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",
- "imageDetails": self.harness.charm.image.fetch(),
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": "prometheus",
- "containerPort": 9090,
- "protocol": "TCP",
- }
- ],
- "envConfig": {},
- "volumeConfig": [
- {
- "name": "config",
- "mountPath": "/etc/prometheus",
- "files": [
- {
- "path": "prometheus.yml",
- "content": (
- "global:\n"
- " scrape_interval: 15s\n"
- " evaluation_interval: 15s\n"
- "alerting:\n"
- " alertmanagers:\n"
- " - static_configs:\n"
- " - targets:\n"
- "rule_files:\n"
- "scrape_configs:\n"
- " - job_name: 'prometheus'\n"
- " static_configs:\n"
- " - targets: [{}]\n".format("")
- ),
- }
- ],
- }
- ],
- "command": [
- "/bin/prometheus",
- "--config.file=/etc/prometheus/prometheus.yml",
- "--storage.tsdb.path=/prometheus",
- "--web.console.libraries=/usr/share/prometheus/console_libraries",
- "--web.console.templates=/usr/share/prometheus/consoles",
- "--web.route-prefix={}".format("/"),
- "--web.external-url=http://localhost:{}{}".format(9090, "/"),
- ],
- "kubernetes": {
- "readinessProbe": {
- "httpGet": {
- "path": "/-/ready",
- "port": 9090,
- },
- "initialDelaySeconds": 10,
- "timeoutSeconds": 30,
- },
- "livenessProbe": {
- "httpGet": {
- "path": "/-/healthy",
- "port": 9090,
- },
- "initialDelaySeconds": 30,
- "periodSeconds": 30,
- },
- },
- }
- ],
- "kubernetesResources": {
- "ingressResources": [
- {
- "name": "prometheus-ingress",
- "annotations": {
- "nginx.ingress.kubernetes.io/proxy-body-size": "0",
- },
- "spec": {
- "rules": [
- {
- "host": "prometheus",
- "http": {
- "paths": [
- {
- "path": "/",
- "backend": {
- "serviceName": "prometheus",
- "servicePort": 9090,
- },
- }
- ]
- },
- }
- ],
- "tls": [
- {"hosts": ["prometheus"], "secretName": "prometheus"}
- ],
- },
- }
- ],
- },
- }
-
- self.harness.charm.on.start.emit()
-
- self.harness.update_config(
- {"site_url": "https://prometheus", "tls_secret_name": "prometheus"}
- )
-
- 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",
- "imageDetails": self.harness.charm.image.fetch(),
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": "prometheus",
- "containerPort": 9090,
- "protocol": "TCP",
- }
- ],
- "envConfig": {},
- "volumeConfig": [
- {
- "name": "config",
- "mountPath": "/etc/prometheus",
- "files": [
- {
- "path": "prometheus.yml",
- "content": (
- "global:\n"
- " scrape_interval: 15s\n"
- " evaluation_interval: 15s\n"
- "alerting:\n"
- " alertmanagers:\n"
- " - static_configs:\n"
- " - targets:\n"
- "rule_files:\n"
- "scrape_configs:\n"
- " - job_name: 'prometheus'\n"
- " static_configs:\n"
- " - targets: [{}]\n".format("")
- ),
- }
- ],
- }
- ],
- "command": [
- "/bin/prometheus",
- "--config.file=/etc/prometheus/prometheus.yml",
- "--storage.tsdb.path=/prometheus",
- "--web.console.libraries=/usr/share/prometheus/console_libraries",
- "--web.console.templates=/usr/share/prometheus/consoles",
- "--web.route-prefix={}".format("/"),
- "--web.external-url=http://localhost:{}{}".format(9090, "/"),
- ],
- "kubernetes": {
- "readinessProbe": {
- "httpGet": {
- "path": "/-/ready",
- "port": 9090,
- },
- "initialDelaySeconds": 10,
- "timeoutSeconds": 30,
- },
- "livenessProbe": {
- "httpGet": {
- "path": "/-/healthy",
- "port": 9090,
- },
- "initialDelaySeconds": 30,
- "periodSeconds": 30,
- },
- },
- }
- ],
- "kubernetesResources": {
- "ingressResources": [
- {
- "name": "prometheus-ingress",
- "annotations": {
- "nginx.ingress.kubernetes.io/proxy-body-size": "0",
- "nginx.ingress.kubernetes.io/whitelist-source-range": "0.0.0.0/0",
- },
- "spec": {
- "rules": [
- {
- "host": "prometheus",
- "http": {
- "paths": [
- {
- "path": "/",
- "backend": {
- "serviceName": "prometheus",
- "servicePort": 9090,
- },
- }
- ]
- },
- }
- ],
- "tls": [
- {"hosts": ["prometheus"], "secretName": "prometheus"}
- ],
- },
- }
- ],
- },
- }
-
- self.harness.charm.on.start.emit()
-
- self.harness.update_config(
- {
- "site_url": "https://prometheus",
- "tls_secret_name": "prometheus",
- "ingress_whitelist_source_range": "0.0.0.0/0",
- }
- )
-
- pod_spec, _ = self.harness.get_pod_spec()
-
- self.assertDictEqual(expected_result, pod_spec)
-
- def test_publish_prometheus_info(self) -> NoReturn:
+ def test_publish_prometheus_info(
+ self,
+ ) -> NoReturn:
"""Test to see if prometheus relation is updated."""
expected_result = {
- "host": "prometheus",
+ "hostname": "prometheus",
"port": "9090",
}
- self.harness.charm.on.start.emit()
+ relation_id = self.harness.add_relation("prometheus", "mon")
+ self.harness.add_relation_unit(relation_id, "mon/0")
+ relation_data = self.harness.get_relation_data(relation_id, "prometheus")
+ self.assertDictEqual(expected_result, relation_data)
+
+ def test_publish_prometheus_info_non_leader(
+ self,
+ ) -> NoReturn:
+ """Test to see if prometheus relation is updated."""
+ expected_result = {}
+
+ self.harness.set_leader(is_leader=False)
relation_id = self.harness.add_relation("prometheus", "mon")
self.harness.add_relation_unit(relation_id, "mon/0")
relation_data = self.harness.get_relation_data(relation_id, "prometheus")
diff --git a/installers/charm/prometheus/tests/test_pod_spec.py b/installers/charm/prometheus/tests/test_pod_spec.py
index 22f6bf5..1adbae6 100644
--- a/installers/charm/prometheus/tests/test_pod_spec.py
+++ b/installers/charm/prometheus/tests/test_pod_spec.py
@@ -286,7 +286,6 @@
]
pod_envconfig = pod_spec._make_pod_files(config)
- print(expected_result, pod_envconfig)
self.assertListEqual(expected_result, pod_envconfig)
def test_make_readiness_probe(self) -> NoReturn:
diff --git a/installers/charm/prometheus/tox.ini b/installers/charm/prometheus/tox.ini
index 5491c07..1f9442e 100644
--- a/installers/charm/prometheus/tox.ini
+++ b/installers/charm/prometheus/tox.ini
@@ -18,64 +18,98 @@
# To get in touch with the maintainers, please contact:
# osm-charmers@lists.launchpad.net
##
+#######################################################################################
[tox]
+envlist = flake8, cover, pylint, safety, yamllint
skipsdist = True
-envlist = unit, lint
-sitepackages = False
-skip_missing_interpreters = False
[testenv]
basepython = python3.8
setenv =
+ VIRTUAL_ENV={envdir}
PYTHONHASHSEED=0
PYTHONPATH = {toxinidir}/src
- CHARM_NAME = prometheus
+deps = -r{toxinidir}/requirements.txt
+#######################################################################################
+[testenv:cover]
+deps = {[testenv]deps}
+ -r{toxinidir}/requirements-test.txt
+commands =
+ sh -c 'rm -f nosetests.xml'
+ coverage erase
+ nose2 -C --coverage src
+ coverage report --omit='*tests*'
+ coverage html -d ./cover --omit='*tests*'
+ coverage xml -o coverage.xml --omit=*tests*
+whitelist_externals = sh
+
+#######################################################################################
+[testenv:safety]
+setenv =
+ LC_ALL=C.UTF-8
+ LANG=C.UTF-8
+deps = {[testenv]deps}
+ -r{toxinidir}/requirements-test.txt
+commands =
+ - safety check --full-report
+
+#######################################################################################
+[testenv:flake8]
+deps = flake8
+commands =
+ flake8 src/ tests/
+
+#######################################################################################
+[testenv:pylint]
+deps = {[testenv]deps}
+ -r{toxinidir}/requirements-test.txt
+ pylint
+commands =
+ pylint -E src
+
+#######################################################################################
+[testenv:black]
+deps = {[testenv]deps}
+ -r{toxinidir}/requirements-test.txt
+ black
+commands = black --check --diff . --exclude "build/|.tox/|mod/|lib/"
+
+#######################################################################################
+[testenv:yamllint]
+deps = {[testenv]deps}
+ -r{toxinidir}/requirements-test.txt
+ yamllint
+commands = yamllint .
+
+#######################################################################################
[testenv:build]
passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY
+deps = {[testenv]deps}
+ -r{toxinidir}/requirements-test.txt
+ charmcraft
whitelist_externals =
charmcraft
- rm
- unzip
+ cp
commands =
- rm -rf release prometheus.charm
charmcraft build
- unzip prometheus.charm -d release
+ cp -r build 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
+#######################################################################################
+[flake8]
+ignore =
+ W291,
+ W293,
+ E123,
+ E125,
+ E226,
+ E241,
+exclude =
+ .git,
+ __pycache__,
+ .tox,
+max-line-length = 120
+show-source = True
+builtins = _
-[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/*