4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain 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,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
21 from cmframework.apis import cmvalidator
22 from cmdatahandlers.api import validation
23 from cmdatahandlers.api import configerror
26 class CaasValidationError(configerror.ConfigError):
27 def __init__(self, description):
28 configerror.ConfigError.__init__(
29 self, 'Validation error in caas_validation: {}'.format(description))
32 class CaasValidationUtils(object):
38 def check_key_in_dict(key, dictionary):
39 if key not in dictionary:
40 raise CaasValidationError("{} cannot be found in {} ".format(key, dictionary))
42 def get_every_key_occurrence(self, var, key):
43 if hasattr(var, 'iteritems'):
44 for k, v in var.iteritems():
47 if isinstance(v, dict):
48 for result in self.get_every_key_occurrence(v, key):
50 elif isinstance(v, list):
52 for result in self.get_every_key_occurrence(d, key):
56 def is_optional_param_present(key, dictionary):
57 if key not in dictionary:
58 logging.info('{} key is not in the config dictionary, since this is an optional '
59 'parameter, validation is skipped.'.format(key))
61 if not dictionary[key]:
62 logging.info('Although {} key is in the config dictionary the correspondig value is '
63 'empty, since this is an optional parametery, '
64 'validation is skipped.'.format(key))
69 class CaasValidation(cmvalidator.CMValidator):
71 SUBSCRIPTION = r'^cloud\.caas|cloud\.hosts|cloud\.networking$'
72 CAAS_DOMAIN = 'cloud.caas'
73 NETW_DOMAIN = 'cloud.networking'
74 HOSTS_DOMAIN = 'cloud.hosts'
76 SERV_PROF = 'service_profiles'
77 CAAS_PROFILE_PATTERN = 'caas_master|caas_worker'
80 DOCKER_SIZE_QOUTA = "docker_size_quota"
81 DOCKER_SIZE_QOUTA_PATTERN = r"^\d*[G,M,K]$"
83 CHART_NAME = "chart_name"
84 CHART_NAME_PATTERN = r"[A-Za-z0-9\.-_]+"
86 CHART_VERSION = "chart_version"
87 CHART_VERSION_PATTERN = r"^\d+\.\d+\.\d+$"
89 HELM_OP_TIMEOUT = "helm_operation_timeout"
91 DOCKER0_CIDR = "docker0_cidr"
93 INSTANTIATION_TIMEOUT = "instantiation_timeout"
95 HELM_PARAMETERS = "helm_parameters"
97 ENCRYPTED_CA = "encrypted_ca"
98 ENCRYPTED_CA_KEY = "encrypted_ca_key"
100 DOMAIN_NAME = "dns_domain"
101 DOMAIN_NAME_PATTERN = "^[a-z0-9]([a-z0-9-\.]{0,253}[a-z0-9])?$"
104 cmvalidator.CMValidator.__init__(self)
105 self.validation_utils = validation.ValidationUtils()
107 self.caas_conf = None
108 self.caas_utils = CaasValidationUtils()
110 def get_subscription_info(self):
111 return self.SUBSCRIPTION
113 def validate_set(self, props):
114 if not self.is_caas_mandatory(props):
115 logging.info("{} not found in {}, caas validation is not needed.".format(
116 self.CAAS_PROFILE_PATTERN, self.HOSTS_DOMAIN))
118 self.props_pre_check(props)
119 self.validate_docker_size_quota()
120 self.validate_chart_name()
121 self.validate_chart_version()
122 self.validate_helm_operation_timeout()
123 self.validate_docker0_cidr(props)
124 self.validate_instantiation_timeout()
125 self.validate_helm_parameters()
126 self.validate_encrypted_ca(self.ENCRYPTED_CA)
127 self.validate_encrypted_ca(self.ENCRYPTED_CA_KEY)
128 self.validate_dns_domain()
130 def is_caas_mandatory(self, props):
131 hosts_conf = json.loads(props[self.HOSTS_DOMAIN])
132 service_profiles = self.caas_utils.get_every_key_occurrence(hosts_conf, self.SERV_PROF)
133 pattern = re.compile(self.CAAS_PROFILE_PATTERN)
134 for profile in service_profiles:
135 if filter(pattern.match, profile):
139 def props_pre_check(self, props):
140 if not isinstance(props, dict):
141 raise CaasValidationError('The given input: {} is not a dictionary!'.format(props))
142 if self.CAAS_DOMAIN not in props:
143 raise CaasValidationError(
144 '{} configuration is missing from {}!'.format(self.CAAS_DOMAIN, props))
145 self.caas_conf = json.loads(props[self.CAAS_DOMAIN])
146 self.conf = {self.CAAS_DOMAIN: self.caas_conf}
147 if not self.caas_conf:
148 raise CaasValidationError('{} is an empty dictionary!'.format(self.conf))
150 def validate_docker_size_quota(self):
151 if not self.caas_utils.is_optional_param_present(self.DOCKER_SIZE_QOUTA, self.caas_conf):
153 if not re.match(self.DOCKER_SIZE_QOUTA_PATTERN, self.caas_conf[self.DOCKER_SIZE_QOUTA]):
154 raise CaasValidationError('{} is not a valid {}!'.format(
155 self.caas_conf[self.DOCKER_SIZE_QOUTA],
156 self.DOCKER_SIZE_QOUTA))
158 def validate_chart_name(self):
159 if not self.caas_utils.is_optional_param_present(self.CHART_NAME, self.caas_conf):
161 if not re.match(self.CHART_NAME_PATTERN, self.caas_conf[self.CHART_NAME]):
162 raise CaasValidationError('{} is not a valid {} !'.format(
163 self.caas_conf[self.CHART_NAME],
166 def validate_chart_version(self):
167 if not self.caas_utils.is_optional_param_present(self.CHART_VERSION, self.caas_conf):
169 if not self.caas_conf[self.CHART_NAME]:
170 logging.warn('{} shall be set only, when {} is set.'.format(
171 self.CHART_VERSION, self.CHART_NAME))
172 if not re.match(self.CHART_VERSION_PATTERN, self.caas_conf[self.CHART_VERSION]):
173 raise CaasValidationError('{} is not a valid {} !'.format(
174 self.caas_conf[self.CHART_VERSION],
177 def validate_helm_operation_timeout(self):
178 if not self.caas_utils.is_optional_param_present(self.HELM_OP_TIMEOUT, self.caas_conf):
180 if not isinstance(self.caas_conf[self.HELM_OP_TIMEOUT], int):
181 raise CaasValidationError('{}:{} is not an integer'.format(
182 self.HELM_OP_TIMEOUT,
183 self.caas_conf[self.HELM_OP_TIMEOUT]))
185 def get_docker0_cidr_netw_obj(self, subnet):
187 return ipaddr.IPNetwork(subnet)
188 except ValueError as exc:
189 raise CaasValidationError('{} is an invalid subnet address: {}'.format(
190 self.DOCKER0_CIDR, exc))
192 def check_docker0_cidr_overlaps_with_netw_subnets(self, docker0_cidr, props):
193 netw_conf = json.loads(props[self.NETW_DOMAIN])
194 cidrs = self.caas_utils.get_every_key_occurrence(netw_conf, self.CIDR)
196 if docker0_cidr.overlaps(ipaddr.IPNetwork(cidr)):
197 raise CaasValidationError(
198 'CIDR configured for {} shall be an unused IP range, '
199 'but it overlaps with {} from {}.'.format(
200 self.DOCKER0_CIDR, cidr, self.NETW_DOMAIN))
202 def validate_docker0_cidr(self, props):
203 if not self.caas_utils.is_optional_param_present(self.DOCKER0_CIDR, self.caas_conf):
205 docker0_cidr_obj = self.get_docker0_cidr_netw_obj(self.caas_conf[self.DOCKER0_CIDR])
206 self.check_docker0_cidr_overlaps_with_netw_subnets(docker0_cidr_obj, props)
208 def validate_instantiation_timeout(self):
209 if not self.caas_utils.is_optional_param_present(self.INSTANTIATION_TIMEOUT,
212 if not isinstance(self.caas_conf[self.INSTANTIATION_TIMEOUT], int):
213 raise CaasValidationError('{}:{} is not an integer'.format(
214 self.INSTANTIATION_TIMEOUT,
215 self.caas_conf[self.INSTANTIATION_TIMEOUT]))
217 def validate_helm_parameters(self):
218 if not self.caas_utils.is_optional_param_present(self.HELM_PARAMETERS, self.caas_conf):
220 if not isinstance(self.caas_conf[self.HELM_PARAMETERS], dict):
221 raise CaasValidationError('The given input: {} is not a dictionary!'.format(
222 self.caas_conf[self.HELM_PARAMETERS]))
224 def validate_encrypted_ca(self, enc_ca):
225 self.caas_utils.check_key_in_dict(enc_ca, self.caas_conf)
226 enc_ca_str = self.caas_conf[enc_ca][0]
228 raise CaasValidationError('{} shall not be empty !'.format(enc_ca))
230 base64.b64decode(enc_ca_str)
231 except TypeError as exc:
232 raise CaasValidationError('Invalid {}: {}'.format(enc_ca, exc))
234 def validate_dns_domain(self):
235 domain = self.caas_conf[self.DOMAIN_NAME]
236 if not self.caas_utils.is_optional_param_present(self.DOMAIN_NAME, self.caas_conf):
238 if not re.match(self.DOMAIN_NAME_PATTERN, domain):
239 raise CaasValidationError('{} is not a valid {} !'.format(