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
: str
64 security_context
: bool
66 @validator("web_subpath")
67 def validate_web_subpath(cls
, v
):
69 raise ValueError("web-subpath must be a non-empty string")
72 @validator("max_file_size")
73 def validate_max_file_size(cls
, v
):
75 raise ValueError("value must be equal or greater than 0")
78 @validator("site_url")
79 def validate_site_url(cls
, v
):
82 if not parsed
.scheme
.startswith("http"):
83 raise ValueError("value must start with http")
86 @validator("ingress_whitelist_source_range")
87 def validate_ingress_whitelist_source_range(cls
, v
):
92 @validator("image_pull_policy")
93 def validate_image_pull_policy(cls
, v
):
96 "ifnotpresent": "IfNotPresent",
100 if v
not in values
.keys():
101 raise ValueError("value must be always, ifnotpresent or never")
105 class PrometheusCharm(CharmedOsmBase
):
107 """Prometheus Charm."""
109 def __init__(self
, *args
) -> NoReturn
:
110 """Prometheus Charm constructor."""
111 super().__init
__(*args
, oci_image
="image")
113 # Registering provided relation events
114 self
.prometheus
= PrometheusServer(self
, "prometheus")
115 self
.framework
.observe(
116 self
.on
.prometheus_relation_joined
, # pylint: disable=E1101
117 self
._publish
_prometheus
_info
,
120 # Registering actions
121 self
.framework
.observe(
122 self
.on
.backup_action
, # pylint: disable=E1101
123 self
._on
_backup
_action
,
126 def _publish_prometheus_info(self
, event
: EventBase
) -> NoReturn
:
127 self
.prometheus
.publish_info(self
.app
.name
, PORT
)
129 def _on_backup_action(self
, event
: EventBase
) -> NoReturn
:
130 url
= f
"http://{self.model.app.name}:{PORT}/api/v1/admin/tsdb/snapshot"
131 result
= requests
.post(url
)
133 if result
.status_code
== 200:
134 event
.set_results({"backup-name": result
.json()["name"]})
136 event
.fail(f
"status-code: {result.status_code}")
138 def _build_files(self
, config
: ConfigModel
):
139 files_builder
= FilesV3Builder()
140 files_builder
.add_file(
144 " scrape_interval: 15s\n"
145 " evaluation_interval: 15s\n"
148 " - static_configs:\n"
152 " - job_name: 'prometheus'\n"
154 f
" - targets: [{config.default_target}]\n"
157 return files_builder
.build()
159 def build_pod_spec(self
, image_info
):
161 config
= ConfigModel(**dict(self
.config
))
162 # Create Builder for the PodSpec
163 pod_spec_builder
= PodSpecV3Builder(
164 enable_security_context
=config
.security_context
167 # Build Backup Container
168 backup_image
= OCIImageResource(self
, "backup-image")
169 backup_image_info
= backup_image
.fetch()
170 backup_container_builder
= ContainerV3Builder("prom-backup", backup_image_info
)
171 backup_container
= backup_container_builder
.build()
172 # Add backup container to pod spec
173 pod_spec_builder
.add_container(backup_container
)
176 container_builder
= ContainerV3Builder(
179 config
.image_pull_policy
,
180 run_as_non_root
=config
.security_context
,
182 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
183 container_builder
.add_http_readiness_probe(
186 initial_delay_seconds
=10,
189 container_builder
.add_http_liveness_probe(
192 initial_delay_seconds
=30,
197 "--config.file=/etc/prometheus/prometheus.yml",
198 "--storage.tsdb.path=/prometheus",
199 "--web.console.libraries=/usr/share/prometheus/console_libraries",
200 "--web.console.templates=/usr/share/prometheus/consoles",
201 f
"--web.route-prefix={config.web_subpath}",
202 f
"--web.external-url=http://localhost:{PORT}{config.web_subpath}",
204 if config
.enable_web_admin_api
:
205 command
.append("--web.enable-admin-api")
206 container_builder
.add_command(command
)
207 container_builder
.add_volume_config(
208 "config", "/etc/prometheus", self
._build
_files
(config
)
210 container
= container_builder
.build()
211 # Add container to pod spec
212 pod_spec_builder
.add_container(container
)
213 # Add ingress resources to pod spec if site url exists
215 parsed
= urlparse(config
.site_url
)
217 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
218 str(config
.max_file_size
) + "m"
219 if config
.max_file_size
> 0
220 else config
.max_file_size
223 if config
.ingress_class
:
224 annotations
["kubernetes.io/ingress.class"] = config
.ingress_class
225 ingress_resource_builder
= IngressResourceV3Builder(
226 f
"{self.app.name}-ingress", annotations
229 if config
.ingress_whitelist_source_range
:
231 "nginx.ingress.kubernetes.io/whitelist-source-range"
232 ] = config
.ingress_whitelist_source_range
234 if config
.cluster_issuer
:
235 annotations
["cert-manager.io/cluster-issuer"] = config
.cluster_issuer
237 if parsed
.scheme
== "https":
238 ingress_resource_builder
.add_tls(
239 [parsed
.hostname
], config
.tls_secret_name
242 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
244 ingress_resource_builder
.add_rule(parsed
.hostname
, self
.app
.name
, PORT
)
245 ingress_resource
= ingress_resource_builder
.build()
246 pod_spec_builder
.add_ingress_resource(ingress_resource
)
247 return pod_spec_builder
.build()
250 if __name__
== "__main__":
251 main(PrometheusCharm
)