Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python3 

2# Copyright 2021 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# pylint: disable=E0213 

24 

25 

26import base64 

27import logging 

28from typing import NoReturn, Optional 

29 

30 

31from ops.main import main 

32from opslib.osm.charm import CharmedOsmBase, RelationsMissing 

33from opslib.osm.interfaces.kafka import KafkaClient 

34from opslib.osm.interfaces.keystone import KeystoneClient 

35from opslib.osm.interfaces.mongo import MongoClient 

36from opslib.osm.interfaces.prometheus import PrometheusClient 

37from opslib.osm.pod import ContainerV3Builder, FilesV3Builder, PodSpecV3Builder 

38from opslib.osm.validator import ModelValidator, validator 

39 

40 

41logger = logging.getLogger(__name__) 

42 

43PORT = 8000 

44 

45 

46def _check_certificate_data(name: str, content: str): 

47 if not name or not content: 

48 raise ValueError("certificate name and content must be a non-empty string") 

49 

50 

51def _extract_certificates(certs_config: str): 

52 certificates = {} 

53 if certs_config: 

54 cert_list = certs_config.split(",") 

55 for cert in cert_list: 

56 name, content = cert.split(":") 

57 _check_certificate_data(name, content) 

58 certificates[name] = content 

59 return certificates 

60 

61 

62def decode(content: str): 

63 return base64.b64decode(content.encode("utf-8")).decode("utf-8") 

64 

65 

66class ConfigModel(ModelValidator): 

67 keystone_enabled: bool 

68 vca_host: str 

69 vca_user: str 

70 vca_secret: str 

71 vca_cacert: str 

72 database_commonkey: str 

73 log_level: str 

74 openstack_default_granularity: int 

75 global_request_timeout: int 

76 collector_interval: int 

77 evaluator_interval: int 

78 grafana_url: str 

79 grafana_user: str 

80 grafana_password: str 

81 certificates: Optional[str] 

82 

83 @validator("log_level") 

84 def validate_log_level(cls, v): 

85 if v not in {"INFO", "DEBUG"}: 

86 raise ValueError("value must be INFO or DEBUG") 

87 return v 

88 

89 @validator("certificates") 

90 def validate_certificates(cls, v): 

91 # Raises an exception if it cannot extract the certificates 

92 _extract_certificates(v) 

93 return v 

94 

95 @property 

96 def certificates_dict(cls): 

97 return _extract_certificates(cls.certificates) if cls.certificates else {} 

98 

99 

100class MonCharm(CharmedOsmBase): 

101 def __init__(self, *args) -> NoReturn: 

102 super().__init__(*args, oci_image="image") 

103 

104 self.kafka_client = KafkaClient(self, "kafka") 

105 self.framework.observe(self.on["kafka"].relation_changed, self.configure_pod) 

106 self.framework.observe(self.on["kafka"].relation_broken, self.configure_pod) 

107 

108 self.mongodb_client = MongoClient(self, "mongodb") 

109 self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod) 

110 self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod) 

111 

112 self.prometheus_client = PrometheusClient(self, "prometheus") 

113 self.framework.observe( 

114 self.on["prometheus"].relation_changed, self.configure_pod 

115 ) 

116 self.framework.observe( 

117 self.on["prometheus"].relation_broken, self.configure_pod 

118 ) 

119 

120 self.keystone_client = KeystoneClient(self, "keystone") 

121 self.framework.observe(self.on["keystone"].relation_changed, self.configure_pod) 

122 self.framework.observe(self.on["keystone"].relation_broken, self.configure_pod) 

123 

124 def _check_missing_dependencies(self, config: ConfigModel): 

125 missing_relations = [] 

126 

127 if self.kafka_client.is_missing_data_in_unit(): 

128 missing_relations.append("kafka") 

129 if self.mongodb_client.is_missing_data_in_unit(): 

130 missing_relations.append("mongodb") 

131 if self.prometheus_client.is_missing_data_in_app(): 

132 missing_relations.append("prometheus") 

133 if config.keystone_enabled: 

134 if self.keystone_client.is_missing_data_in_app(): 

135 missing_relations.append("keystone") 

136 

137 if missing_relations: 

138 raise RelationsMissing(missing_relations) 

139 

140 def _build_cert_files( 

141 self, 

142 config: ConfigModel, 

143 ): 

144 cert_files_builder = FilesV3Builder() 

145 for name, content in config.certificates_dict.items(): 

146 cert_files_builder.add_file(name, decode(content), mode=0o600) 

147 return cert_files_builder.build() 

148 

149 def build_pod_spec(self, image_info): 

150 # Validate config 

151 config = ConfigModel(**dict(self.config)) 

152 # Check relations 

153 self._check_missing_dependencies(config) 

154 # Create Builder for the PodSpec 

155 pod_spec_builder = PodSpecV3Builder() 

156 # Build Container 

157 container_builder = ContainerV3Builder(self.app.name, image_info) 

158 certs_files = self._build_cert_files(config) 

159 if certs_files: 

160 container_builder.add_volume_config("certs", "/certs", certs_files) 

161 container_builder.add_port(name=self.app.name, port=PORT) 

162 container_builder.add_envs( 

163 { 

164 # General configuration 

165 "ALLOW_ANONYMOUS_LOGIN": "yes", 

166 "OSMMON_OPENSTACK_DEFAULT_GRANULARITY": config.openstack_default_granularity, 

167 "OSMMON_GLOBAL_REQUEST_TIMEOUT": config.global_request_timeout, 

168 "OSMMON_GLOBAL_LOGLEVEL": config.log_level, 

169 "OSMMON_COLLECTOR_INTERVAL": config.collector_interval, 

170 "OSMMON_EVALUATOR_INTERVAL": config.evaluator_interval, 

171 # Kafka configuration 

172 "OSMMON_MESSAGE_DRIVER": "kafka", 

173 "OSMMON_MESSAGE_HOST": self.kafka_client.host, 

174 "OSMMON_MESSAGE_PORT": self.kafka_client.port, 

175 # Database configuration 

176 "OSMMON_DATABASE_DRIVER": "mongo", 

177 "OSMMON_DATABASE_URI": self.mongodb_client.connection_string, 

178 "OSMMON_DATABASE_COMMONKEY": config.database_commonkey, 

179 # Prometheus configuration 

180 "OSMMON_PROMETHEUS_URL": f"http://{self.prometheus_client.hostname}:{self.prometheus_client.port}", 

181 # VCA configuration 

182 "OSMMON_VCA_HOST": config.vca_host, 

183 "OSMMON_VCA_USER": config.vca_user, 

184 "OSMMON_VCA_SECRET": config.vca_secret, 

185 "OSMMON_VCA_CACERT": config.vca_cacert, 

186 "OSMMON_GRAFANA_URL": config.grafana_url, 

187 "OSMMON_GRAFANA_USER": config.grafana_user, 

188 "OSMMON_GRAFANA_PASSWORD": config.grafana_password, 

189 } 

190 ) 

191 if config.keystone_enabled: 

192 container_builder.add_envs( 

193 { 

194 "OSMMON_KEYSTONE_ENABLED": True, 

195 "OSMMON_KEYSTONE_URL": self.keystone_client.host, 

196 "OSMMON_KEYSTONE_DOMAIN_NAME": self.keystone_client.user_domain_name, 

197 "OSMMON_KEYSTONE_PROJECT_DOMAIN_NAME": self.keystone_client.project_domain_name, 

198 "OSMMON_KEYSTONE_SERVICE_USER": self.keystone_client.username, 

199 "OSMMON_KEYSTONE_SERVICE_PASSWORD": self.keystone_client.password, 

200 "OSMMON_KEYSTONE_SERVICE_PROJECT": self.keystone_client.service, 

201 } 

202 ) 

203 

204 container = container_builder.build() 

205 # Add container to pod spec 

206 pod_spec_builder.add_container(container) 

207 return pod_spec_builder.build() 

208 

209 

210if __name__ == "__main__": 

211 main(MonCharm)