2 # Copyright 2021 Canonical Ltd.
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
5 # not use this file except in compliance with the License. You may obtain
6 # a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations
16 # For those usages not covered by the Apache License, Version 2.0 please
17 # contact: legal@canonical.com
19 # To get in touch with the maintainers, please contact:
20 # osm-charmers@lists.launchpad.net
23 # pylint: disable=E0213
25 from ipaddress
import ip_network
27 from typing
import NoReturn
, Optional
28 from urllib
.parse
import urlparse
30 from oci_image
import OCIImageResource
31 from ops
.framework
import EventBase
32 from ops
.main
import main
33 from opslib
.osm
.charm
import CharmedOsmBase
34 from opslib
.osm
.interfaces
.prometheus
import PrometheusServer
35 from opslib
.osm
.pod
import (
38 IngressResourceV3Builder
,
41 from opslib
.osm
.validator
import (
48 logger
= logging
.getLogger(__name__
)
53 class ConfigModel(ModelValidator
):
57 site_url
: Optional
[str]
58 cluster_issuer
: Optional
[str]
59 ingress_class
: Optional
[str]
60 ingress_whitelist_source_range
: Optional
[str]
61 tls_secret_name
: Optional
[str]
62 enable_web_admin_api
: bool
63 image_pull_policy
: Optional
[str]
65 @validator("web_subpath")
66 def validate_web_subpath(cls
, v
):
68 raise ValueError("web-subpath must be a non-empty string")
71 @validator("max_file_size")
72 def validate_max_file_size(cls
, v
):
74 raise ValueError("value must be equal or greater than 0")
77 @validator("site_url")
78 def validate_site_url(cls
, v
):
81 if not parsed
.scheme
.startswith("http"):
82 raise ValueError("value must start with http")
85 @validator("ingress_whitelist_source_range")
86 def validate_ingress_whitelist_source_range(cls
, v
):
91 @validator("image_pull_policy")
92 def validate_image_pull_policy(cls
, v
):
95 "ifnotpresent": "IfNotPresent",
99 if v
not in values
.keys():
100 raise ValueError("value must be always, ifnotpresent or never")
104 class PrometheusCharm(CharmedOsmBase
):
106 """Prometheus Charm."""
108 def __init__(self
, *args
) -> NoReturn
:
109 """Prometheus Charm constructor."""
110 super().__init
__(*args
, oci_image
="image")
112 # Registering provided relation events
113 self
.prometheus
= PrometheusServer(self
, "prometheus")
114 self
.framework
.observe(
115 self
.on
.prometheus_relation_joined
, # pylint: disable=E1101
116 self
._publish
_prometheus
_info
,
119 # Registering actions
120 self
.framework
.observe(
121 self
.on
.backup_action
, # pylint: disable=E1101
122 self
._on
_backup
_action
,
125 def _publish_prometheus_info(self
, event
: EventBase
) -> NoReturn
:
126 self
.prometheus
.publish_info(self
.app
.name
, PORT
)
128 def _on_backup_action(self
, event
: EventBase
) -> NoReturn
:
129 url
= f
"http://{self.model.app.name}:{PORT}/api/v1/admin/tsdb/snapshot"
130 result
= requests
.post(url
)
132 if result
.status_code
== 200:
133 event
.set_results({"backup-name": result
.json()["name"]})
135 event
.fail(f
"status-code: {result.status_code}")
137 def _build_files(self
, config
: ConfigModel
):
138 files_builder
= FilesV3Builder()
139 files_builder
.add_file(
143 " scrape_interval: 15s\n"
144 " evaluation_interval: 15s\n"
147 " - static_configs:\n"
151 " - job_name: 'prometheus'\n"
153 f
" - targets: [{config.default_target}]\n"
156 return files_builder
.build()
158 def build_pod_spec(self
, image_info
):
160 config
= ConfigModel(**dict(self
.config
))
161 # Create Builder for the PodSpec
162 pod_spec_builder
= PodSpecV3Builder()
164 # Build Backup Container
165 backup_image
= OCIImageResource(self
, "backup-image")
166 backup_image_info
= backup_image
.fetch()
167 backup_container_builder
= ContainerV3Builder("prom-backup", backup_image_info
)
168 backup_container
= backup_container_builder
.build()
169 # Add backup container to pod spec
170 pod_spec_builder
.add_container(backup_container
)
173 container_builder
= ContainerV3Builder(
174 self
.app
.name
, image_info
, config
.image_pull_policy
176 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
177 container_builder
.add_http_readiness_probe(
180 initial_delay_seconds
=10,
183 container_builder
.add_http_liveness_probe(
186 initial_delay_seconds
=30,
191 "--config.file=/etc/prometheus/prometheus.yml",
192 "--storage.tsdb.path=/prometheus",
193 "--web.console.libraries=/usr/share/prometheus/console_libraries",
194 "--web.console.templates=/usr/share/prometheus/consoles",
195 f
"--web.route-prefix={config.web_subpath}",
196 f
"--web.external-url=http://localhost:{PORT}{config.web_subpath}",
198 if config
.enable_web_admin_api
:
199 command
.append("--web.enable-admin-api")
200 container_builder
.add_command(command
)
201 container_builder
.add_volume_config(
202 "config", "/etc/prometheus", self
._build
_files
(config
)
204 container
= container_builder
.build()
205 # Add container to pod spec
206 pod_spec_builder
.add_container(container
)
207 # Add ingress resources to pod spec if site url exists
209 parsed
= urlparse(config
.site_url
)
211 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
212 str(config
.max_file_size
) + "m"
213 if config
.max_file_size
> 0
214 else config
.max_file_size
217 if config
.ingress_class
:
218 annotations
["kubernetes.io/ingress.class"] = config
.ingress_class
219 ingress_resource_builder
= IngressResourceV3Builder(
220 f
"{self.app.name}-ingress", annotations
223 if config
.ingress_whitelist_source_range
:
225 "nginx.ingress.kubernetes.io/whitelist-source-range"
226 ] = config
.ingress_whitelist_source_range
228 if config
.cluster_issuer
:
229 annotations
["cert-manager.io/cluster-issuer"] = config
.cluster_issuer
231 if parsed
.scheme
== "https":
232 ingress_resource_builder
.add_tls(
233 [parsed
.hostname
], config
.tls_secret_name
236 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
238 ingress_resource_builder
.add_rule(parsed
.hostname
, self
.app
.name
, PORT
)
239 ingress_resource
= ingress_resource_builder
.build()
240 pod_spec_builder
.add_ingress_resource(ingress_resource
)
241 return pod_spec_builder
.build()
244 if __name__
== "__main__":
245 main(PrometheusCharm
)