Skip to content
Snippets Groups Projects
Commit 903379cd authored by sousaedu's avatar sousaedu Committed by Mark Beierl
Browse files

Adding Prometheus Mongodb Exporter Charm


Change-Id: I6d157a79f8ae6be2a5675692c72eeceb796d42e8
Signed-off-by: default avatarsousaedu <eduardo.sousa@canonical.com>
parent a070ce73
No related branches found
No related tags found
No related merge requests found
Showing
with 2495 additions and 0 deletions
# 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
# 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/
<!-- 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 -->
# Prometheus Mongodb Exporter operator Charm for Kubernetes
## Requirements
# 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: ""
# 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
# 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
# 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
#!/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)
#!/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 [],
},
}
#!/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
#!/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()
#!/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()
# 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/*
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment