Refactoring LCM charm to use Operator Framework
[osm/devops.git] / installers / charm / lcm / src / pod_spec.py
1 #!/usr/bin/env python3
2 # Copyright 2020 Canonical Ltd.
3 #
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
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
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
14 # under the License.
15 #
16 # For those usages not covered by the Apache License, Version 2.0 please
17 # contact: legal@canonical.com
18 #
19 # To get in touch with the maintainers, please contact:
20 # osm-charmers@lists.launchpad.net
21 ##
22
23 import logging
24 from pydantic import BaseModel, constr, PositiveInt, validator
25 from typing import Any, Dict, List, Optional
26
27 logger = logging.getLogger(__name__)
28
29
30 class ConfigData(BaseModel):
31 """Configuration data model."""
32
33 database_commonkey: constr(min_length=1)
34 log_level: constr(regex=r"^(INFO|DEBUG)$")
35 vca_host: constr(min_length=1)
36 vca_port: PositiveInt
37 vca_user: constr(min_length=1)
38 vca_pubkey: constr(min_length=1)
39 vca_password: constr(min_length=1)
40 vca_cacert: str
41 vca_cloud: constr(min_length=1)
42 vca_k8s_cloud: constr(min_length=1)
43 vca_apiproxy: Optional[constr(min_length=1)]
44
45 @validator("vca_apiproxy", pre=True, always=True)
46 def validate_vca_apiproxy(cls, value, values, **kwargs):
47 if not value:
48 return None
49
50 return value
51
52
53 class RelationData(BaseModel):
54 """Relation data model."""
55
56 ro_host: constr(min_length=1)
57 ro_port: PositiveInt
58 message_host: constr(min_length=1)
59 message_port: PositiveInt
60 database_uri: constr(regex=r"^(mongodb://)")
61
62
63 def _make_pod_ports(port: int) -> List[Dict[str, Any]]:
64 """Generate pod ports details.
65
66 Args:
67 port (int): port to expose.
68
69 Returns:
70 List[Dict[str, Any]]: pod port details.
71 """
72 return [{"name": "lcm", "containerPort": port, "protocol": "TCP"}]
73
74
75 def _make_pod_envconfig(
76 config: Dict[str, Any], relation_state: Dict[str, Any]
77 ) -> Dict[str, Any]:
78 """Generate pod environment configuration.
79
80 Args:
81 config (Dict[str, Any]): configuration information.
82 relation_state (Dict[str, Any]): relation state information.
83
84 Returns:
85 Dict[str, Any]: pod environment configuration.
86 """
87 envconfig = {
88 # General configuration
89 "ALLOW_ANONYMOUS_LOGIN": "yes",
90 "OSMLCM_GLOBAL_LOGLEVEL": config["log_level"],
91 # RO configuration
92 "OSMLCM_RO_HOST": relation_state["ro_host"],
93 "OSMLCM_RO_PORT": relation_state["ro_port"],
94 "OSMLCM_RO_TENANT": "osm",
95 # Kafka configuration
96 "OSMLCM_MESSAGE_DRIVER": "kafka",
97 "OSMLCM_MESSAGE_HOST": relation_state["message_host"],
98 "OSMLCM_MESSAGE_PORT": relation_state["message_port"],
99 # Database configuration
100 "OSMLCM_DATABASE_DRIVER": "mongo",
101 "OSMLCM_DATABASE_URI": relation_state["database_uri"],
102 "OSMLCM_DATABASE_COMMONKEY": config["database_commonkey"],
103 # Storage configuration
104 "OSMLCM_STORAGE_DRIVER": "mongo",
105 "OSMLCM_STORAGE_PATH": "/app/storage",
106 "OSMLCM_STORAGE_COLLECTION": "files",
107 "OSMLCM_STORAGE_URI": relation_state["database_uri"],
108 # VCA configuration
109 "OSMLCM_VCA_HOST": config["vca_host"],
110 "OSMLCM_VCA_PORT": config["vca_port"],
111 "OSMLCM_VCA_USER": config["vca_user"],
112 "OSMLCM_VCA_PUBKEY": config["vca_pubkey"],
113 "OSMLCM_VCA_SECRET": config["vca_password"],
114 "OSMLCM_VCA_CACERT": config["vca_cacert"],
115 "OSMLCM_VCA_CLOUD": config["vca_cloud"],
116 "OSMLCM_VCA_K8S_CLOUD": config["vca_k8s_cloud"],
117 }
118
119 if "vca_apiproxy" in config and config["vca_apiproxy"]:
120 envconfig["OSMLCM_VCA_APIPROXY"] = config["vca_apiproxy"]
121
122 return envconfig
123
124
125 def _make_startup_probe() -> Dict[str, Any]:
126 """Generate startup probe.
127
128 Returns:
129 Dict[str, Any]: startup probe.
130 """
131 return {
132 "exec": {"command": ["/usr/bin/pgrep python3"]},
133 "initialDelaySeconds": 60,
134 "timeoutSeconds": 5,
135 }
136
137
138 def _make_readiness_probe(port: int) -> Dict[str, Any]:
139 """Generate readiness probe.
140
141 Args:
142 port (int): [description]
143
144 Returns:
145 Dict[str, Any]: readiness probe.
146 """
147 return {
148 "httpGet": {
149 "path": "/osm/",
150 "port": port,
151 },
152 "initialDelaySeconds": 45,
153 "timeoutSeconds": 5,
154 }
155
156
157 def _make_liveness_probe(port: int) -> Dict[str, Any]:
158 """Generate liveness probe.
159
160 Args:
161 port (int): [description]
162
163 Returns:
164 Dict[str, Any]: liveness probe.
165 """
166 return {
167 "httpGet": {
168 "path": "/osm/",
169 "port": port,
170 },
171 "initialDelaySeconds": 45,
172 "timeoutSeconds": 5,
173 }
174
175
176 def make_pod_spec(
177 image_info: Dict[str, str],
178 config: Dict[str, Any],
179 relation_state: Dict[str, Any],
180 app_name: str = "lcm",
181 port: int = 9999,
182 ) -> Dict[str, Any]:
183 """Generate the pod spec information.
184
185 Args:
186 image_info (Dict[str, str]): Object provided by
187 OCIImageResource("image").fetch().
188 config (Dict[str, Any]): Configuration information.
189 relation_state (Dict[str, Any]): Relation state information.
190 app_name (str, optional): Application name. Defaults to "lcm".
191 port (int, optional): Port for the container. Defaults to 9999.
192
193 Returns:
194 Dict[str, Any]: Pod spec dictionary for the charm.
195 """
196 if not image_info:
197 return None
198
199 ConfigData(**(config))
200 RelationData(**(relation_state))
201
202 ports = _make_pod_ports(port)
203 env_config = _make_pod_envconfig(config, relation_state)
204
205 return {
206 "version": 3,
207 "containers": [
208 {
209 "name": app_name,
210 "imageDetails": image_info,
211 "imagePullPolicy": "Always",
212 "ports": ports,
213 "envConfig": env_config,
214 }
215 ],
216 "kubernetesResources": {
217 "ingressResources": [],
218 },
219 }