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):
70 SUBSCRIPTION = r'^cloud\.caas|cloud\.hosts|cloud\.networking|cloud\.network_profiles$'
71 CAAS_DOMAIN = 'cloud.caas'
72 HOSTS_DOMAIN = 'cloud.hosts'
73 NETW_DOMAIN = 'cloud.networking'
74 NETPROF_DOMAIN = 'cloud.network_profiles'
76 SERV_PROF = 'service_profiles'
77 CAAS_PROFILE_PATTERN = 'caas_master|caas_worker'
80 DOCKER_SIZE_QUOTA = "docker_size_quota"
81 DOCKER_SIZE_QUOTA_PATTERN = r"^\d*[G,M,K]$"
83 HELM_OP_TIMEOUT = "helm_operation_timeout"
85 DOCKER0_CIDR = "docker0_cidr"
87 INSTANTIATION_TIMEOUT = "instantiation_timeout"
89 ENCRYPTED_CA = "encrypted_ca"
90 ENCRYPTED_CA_KEY = "encrypted_ca_key"
92 CLUSTER_NETS = 'cluster_networks'
93 TENANT_NETS = 'tenant_networks'
96 cmvalidator.CMValidator.__init__(self)
97 self.validation_utils = validation.ValidationUtils()
100 self.caas_utils = CaasValidationUtils()
102 def get_subscription_info(self):
103 return self.SUBSCRIPTION
105 def validate_set(self, props):
106 if not self.is_caas_mandatory(props):
107 logging.info("{} not found in {}, caas validation is not needed.".format(
108 self.CAAS_PROFILE_PATTERN, self.HOSTS_DOMAIN))
110 self.props_pre_check(props)
111 self.validate_docker_size_quota()
112 self.validate_helm_operation_timeout()
113 self.validate_docker0_cidr(props)
114 self.validate_instantiation_timeout()
115 self.validate_encrypted_ca(self.ENCRYPTED_CA)
116 self.validate_encrypted_ca(self.ENCRYPTED_CA_KEY)
117 self.validate_networks(props)
119 def _get_conf(self, props, domain):
120 if props.get(domain):
121 conf_str = props[domain]
123 conf_str = self.get_plugin_client().get_property(domain)
124 return json.loads(conf_str)
126 def is_caas_mandatory(self, props):
127 if not isinstance(props, dict):
128 raise CaasValidationError('The given input: {} is not a dictionary!'.format(props))
129 hosts_conf = self._get_conf(props, self.HOSTS_DOMAIN)
130 service_profiles = self.caas_utils.get_every_key_occurrence(hosts_conf, self.SERV_PROF)
131 pattern = re.compile(self.CAAS_PROFILE_PATTERN)
132 for profile in service_profiles:
133 if filter(pattern.match, profile):
137 def props_pre_check(self, props):
138 self.caas_conf = self._get_conf(props, self.CAAS_DOMAIN)
139 self.conf = {self.CAAS_DOMAIN: self.caas_conf}
140 if not self.caas_conf:
141 raise CaasValidationError('{} is an empty dictionary!'.format(self.conf))
143 def validate_docker_size_quota(self):
144 if not self.caas_utils.is_optional_param_present(self.DOCKER_SIZE_QUOTA, self.caas_conf):
146 if not re.match(self.DOCKER_SIZE_QUOTA_PATTERN, self.caas_conf[self.DOCKER_SIZE_QUOTA]):
147 raise CaasValidationError(
148 '{} is not a valid {}!'.format(self.caas_conf[self.DOCKER_SIZE_QUOTA],
149 self.DOCKER_SIZE_QUOTA))
151 def validate_helm_operation_timeout(self):
152 if not self.caas_utils.is_optional_param_present(self.HELM_OP_TIMEOUT, self.caas_conf):
154 if not isinstance(self.caas_conf[self.HELM_OP_TIMEOUT], int):
155 raise CaasValidationError(
156 '{}:{} is not an integer'.format(self.HELM_OP_TIMEOUT,
157 self.caas_conf[self.HELM_OP_TIMEOUT]))
159 def get_docker0_cidr_netw_obj(self, subnet):
161 return ipaddr.IPNetwork(subnet)
162 except ValueError as exc:
163 raise CaasValidationError('{} is an invalid subnet address: {}'.format(
164 self.DOCKER0_CIDR, exc))
166 def check_docker0_cidr_overlaps_with_netw_subnets(self, docker0_cidr, props):
167 netw_conf = self._get_conf(props, self.NETW_DOMAIN)
168 cidrs = self.caas_utils.get_every_key_occurrence(netw_conf, self.CIDR)
170 if docker0_cidr.overlaps(ipaddr.IPNetwork(cidr)):
171 raise CaasValidationError(
172 'CIDR configured for {} shall be an unused IP range, '
173 'but it overlaps with {} from {}.'.format(self.DOCKER0_CIDR, cidr,
176 def validate_docker0_cidr(self, props):
177 if not self.caas_utils.is_optional_param_present(self.DOCKER0_CIDR, self.caas_conf):
179 docker0_cidr_obj = self.get_docker0_cidr_netw_obj(self.caas_conf[self.DOCKER0_CIDR])
180 self.check_docker0_cidr_overlaps_with_netw_subnets(docker0_cidr_obj, props)
182 def validate_instantiation_timeout(self):
183 if not self.caas_utils.is_optional_param_present(self.INSTANTIATION_TIMEOUT,
186 if not isinstance(self.caas_conf[self.INSTANTIATION_TIMEOUT], int):
187 raise CaasValidationError('{}:{} is not an integer'.format(
188 self.INSTANTIATION_TIMEOUT, self.caas_conf[self.INSTANTIATION_TIMEOUT]))
190 def validate_encrypted_ca(self, enc_ca):
191 self.caas_utils.check_key_in_dict(enc_ca, self.caas_conf)
192 enc_ca_str = self.caas_conf[enc_ca][0]
194 raise CaasValidationError('{} shall not be empty !'.format(enc_ca))
196 base64.b64decode(enc_ca_str)
197 except TypeError as exc:
198 raise CaasValidationError('Invalid {}: {}'.format(enc_ca, exc))
200 def validate_networks(self, props):
202 for nets_key in [self.CLUSTER_NETS, self.TENANT_NETS]:
203 if self.caas_utils.is_optional_param_present(nets_key, self.caas_conf):
204 if not isinstance(self.caas_conf[nets_key], list):
205 raise CaasValidationError('{} is not a list'.format(nets_key))
206 if len(set(self.caas_conf[nets_key])) != len(self.caas_conf[nets_key]):
207 raise CaasValidationError('{} has duplicate entries'.format(nets_key))
208 caas_nets.extend(self.caas_conf[nets_key])
209 if len(set(caas_nets)) != len(caas_nets):
210 raise CaasValidationError('{} and {} must be distinct, but same entries are '
211 'found from both lists'.format(self.CLUSTER_NETS,
213 self._validate_homogenous_net_setup(props, caas_nets)
215 def _validate_homogenous_net_setup(self, props, caas_nets):
216 # Validate homogenous CaaS provider network setup
217 # pylint: disable=too-many-locals,too-many-nested-blocks
218 hosts_conf = self._get_conf(props, self.HOSTS_DOMAIN)
219 netprof_conf = self._get_conf(props, self.NETPROF_DOMAIN)
221 for net in caas_nets:
222 net_iface_map[net] = None
223 for host, host_conf in hosts_conf.iteritems():
224 # Validate only nodes that can host containerized workloads
225 if ('caas_worker' in host_conf[self.SERV_PROF] or
226 ('caas_master' in host_conf[self.SERV_PROF] and
227 'compute' not in host_conf[self.SERV_PROF])):
228 # Validating CaaS network 'net' mapping in 'host'
229 is_caas_network_present = False
230 profiles = host_conf.get('network_profiles')
231 if isinstance(profiles, list) and profiles:
232 net_prof = netprof_conf.get(profiles[0])
233 if net_prof is not None:
234 ifaces = net_prof.get('provider_network_interfaces', {})
235 for iface, data in ifaces.iteritems():
236 net_type = data.get('type')
237 networks = data.get('provider_networks', [])
238 if net in networks and net_type == 'caas':
239 is_caas_network_present = True
240 if net_iface_map[net] is None:
241 net_iface_map[net] = iface
242 elif net_iface_map[net] != iface:
243 msg = 'CaaS network {} mapped to interface {} in one host '
244 msg += 'and interface {} in another host'
245 raise CaasValidationError(msg.format(net, iface,
248 if not is_caas_network_present:
249 raise CaasValidationError('CaaS network {} missing from host {}'