d5ba34ecc8a7e2a953f4a3a54f16fe0ec1de4cf6
[ta/cm-plugins.git] / validators / src / CaasValidation.py
1 #!/usr/bin/python
2 # Copyright 2019 Nokia
3 #
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
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,
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.
15
16 import json
17 import re
18 import base64
19 import logging
20 import ipaddr
21 from cmframework.apis import cmvalidator
22 from cmdatahandlers.api import validation
23 from cmdatahandlers.api import configerror
24
25
26 class CaasValidationError(configerror.ConfigError):
27     def __init__(self, description):
28         configerror.ConfigError.__init__(
29             self, 'Validation error in caas_validation: {}'.format(description))
30
31
32 class CaasValidationUtils(object):
33
34     def __init__(self):
35         pass
36
37     @staticmethod
38     def check_key_in_dict(key, dictionary):
39         if key not in dictionary:
40             raise CaasValidationError("{} cannot be found in {} ".format(key, dictionary))
41
42     def get_every_key_occurrence(self, var, key):
43         if hasattr(var, 'iteritems'):
44             for k, v in var.iteritems():
45                 if k == key:
46                     yield v
47                 if isinstance(v, dict):
48                     for result in self.get_every_key_occurrence(v, key):
49                         yield result
50                 elif isinstance(v, list):
51                     for d in v:
52                         for result in self.get_every_key_occurrence(d, key):
53                             yield result
54
55     @staticmethod
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))
60             return False
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))
65             return False
66         return True
67
68
69 class CaasValidation(cmvalidator.CMValidator):
70
71     SUBSCRIPTION = r'^cloud\.caas|cloud\.hosts|cloud\.networking$'
72     CAAS_DOMAIN = 'cloud.caas'
73     NETW_DOMAIN = 'cloud.networking'
74     HOSTS_DOMAIN = 'cloud.hosts'
75
76     SERV_PROF = 'service_profiles'
77     CAAS_PROFILE_PATTERN = 'caas_master|caas_worker'
78     CIDR = 'cidr'
79
80     DOCKER_SIZE_QOUTA = "docker_size_quota"
81     DOCKER_SIZE_QOUTA_PATTERN = r"^\d*[G,M,K]$"
82
83     CHART_NAME = "chart_name"
84     CHART_NAME_PATTERN = r"[A-Za-z0-9\.-_]+"
85
86     CHART_VERSION = "chart_version"
87     CHART_VERSION_PATTERN = r"^\d+\.\d+\.\d+$"
88
89     HELM_OP_TIMEOUT = "helm_operation_timeout"
90
91     DOCKER0_CIDR = "docker0_cidr"
92
93     INSTANTIATION_TIMEOUT = "instantiation_timeout"
94
95     HELM_PARAMETERS = "helm_parameters"
96
97     ENCRYPTED_CA = "encrypted_ca"
98     ENCRYPTED_CA_KEY = "encrypted_ca_key"
99
100     def __init__(self):
101         cmvalidator.CMValidator.__init__(self)
102         self.validation_utils = validation.ValidationUtils()
103         self.conf = None
104         self.caas_conf = None
105         self.caas_utils = CaasValidationUtils()
106
107     def get_subscription_info(self):
108         return self.SUBSCRIPTION
109
110     def validate_set(self, props):
111         if not self.is_caas_mandatory(props):
112             logging.info("{} not found in {}, caas validation is not needed.".format(
113                 self.CAAS_PROFILE_PATTERN, self.HOSTS_DOMAIN))
114             return
115         self.props_pre_check(props)
116         self.validate_docker_size_quota()
117         self.validate_chart_name()
118         self.validate_chart_version()
119         self.validate_helm_operation_timeout()
120         self.validate_docker0_cidr(props)
121         self.validate_instantiation_timeout()
122         self.validate_helm_parameters()
123         self.validate_encrypted_ca(self.ENCRYPTED_CA)
124         self.validate_encrypted_ca(self.ENCRYPTED_CA_KEY)
125
126     def is_caas_mandatory(self, props):
127         hosts_conf = json.loads(props[self.HOSTS_DOMAIN])
128         service_profiles = self.caas_utils.get_every_key_occurrence(hosts_conf, self.SERV_PROF)
129         pattern = re.compile(self.CAAS_PROFILE_PATTERN)
130         for profile in service_profiles:
131             if filter(pattern.match, profile):
132                 return True
133         return False
134
135     def props_pre_check(self, props):
136         if not isinstance(props, dict):
137             raise CaasValidationError('The given input: {} is not a dictionary!'.format(props))
138         if self.CAAS_DOMAIN not in props:
139             raise CaasValidationError(
140                 '{} configuration is missing from {}!'.format(self.CAAS_DOMAIN, props))
141         self.caas_conf = json.loads(props[self.CAAS_DOMAIN])
142         self.conf = {self.CAAS_DOMAIN: self.caas_conf}
143         if not self.caas_conf:
144             raise CaasValidationError('{} is an empty dictionary!'.format(self.conf))
145
146     def validate_docker_size_quota(self):
147         if not self.caas_utils.is_optional_param_present(self.DOCKER_SIZE_QOUTA, self.caas_conf):
148             return
149         if not re.match(self.DOCKER_SIZE_QOUTA_PATTERN, self.caas_conf[self.DOCKER_SIZE_QOUTA]):
150             raise CaasValidationError('{} is not a valid {}!'.format(
151                 self.caas_conf[self.DOCKER_SIZE_QOUTA],
152                 self.DOCKER_SIZE_QOUTA))
153
154     def validate_chart_name(self):
155         if not self.caas_utils.is_optional_param_present(self.CHART_NAME, self.caas_conf):
156             return
157         if not re.match(self.CHART_NAME_PATTERN, self.caas_conf[self.CHART_NAME]):
158             raise CaasValidationError('{} is not a valid {} !'.format(
159                 self.caas_conf[self.CHART_NAME],
160                 self.CHART_NAME))
161
162     def validate_chart_version(self):
163         if not self.caas_utils.is_optional_param_present(self.CHART_VERSION, self.caas_conf):
164             return
165         if not self.caas_conf[self.CHART_NAME]:
166             logging.warn('{} shall be set only, when {} is set.'.format(
167                 self.CHART_VERSION, self.CHART_NAME))
168         if not re.match(self.CHART_VERSION_PATTERN, self.caas_conf[self.CHART_VERSION]):
169             raise CaasValidationError('{} is not a valid {} !'.format(
170                 self.caas_conf[self.CHART_VERSION],
171                 self.CHART_VERSION))
172
173     def validate_helm_operation_timeout(self):
174         if not self.caas_utils.is_optional_param_present(self.HELM_OP_TIMEOUT, self.caas_conf):
175             return
176         if not isinstance(self.caas_conf[self.HELM_OP_TIMEOUT], int):
177             raise CaasValidationError('{}:{} is not an integer'.format(
178                 self.HELM_OP_TIMEOUT,
179                 self.caas_conf[self.HELM_OP_TIMEOUT]))
180
181     def get_docker0_cidr_netw_obj(self, subnet):
182         try:
183             return ipaddr.IPNetwork(subnet)
184         except ValueError as exc:
185             raise CaasValidationError('{} is an invalid subnet address: {}'.format(
186                 self.DOCKER0_CIDR, exc))
187
188     def check_docker0_cidr_overlaps_with_netw_subnets(self, docker0_cidr, props):
189         netw_conf = json.loads(props[self.NETW_DOMAIN])
190         cidrs = self.caas_utils.get_every_key_occurrence(netw_conf, self.CIDR)
191         for cidr in cidrs:
192             if docker0_cidr.overlaps(ipaddr.IPNetwork(cidr)):
193                 raise CaasValidationError(
194                     'CIDR configured for {} shall be an unused IP range, '
195                     'but it overlaps with {} from {}.'.format(
196                         self.DOCKER0_CIDR, cidr, self.NETW_DOMAIN))
197
198     def validate_docker0_cidr(self, props):
199         if not self.caas_utils.is_optional_param_present(self.DOCKER0_CIDR, self.caas_conf):
200             return
201         docker0_cidr_obj = self.get_docker0_cidr_netw_obj(self.caas_conf[self.DOCKER0_CIDR])
202         self.check_docker0_cidr_overlaps_with_netw_subnets(docker0_cidr_obj, props)
203
204     def validate_instantiation_timeout(self):
205         if not self.caas_utils.is_optional_param_present(self.INSTANTIATION_TIMEOUT,
206                                                          self.caas_conf):
207             return
208         if not isinstance(self.caas_conf[self.INSTANTIATION_TIMEOUT], int):
209             raise CaasValidationError('{}:{} is not an integer'.format(
210                 self.INSTANTIATION_TIMEOUT,
211                 self.caas_conf[self.INSTANTIATION_TIMEOUT]))
212
213     def validate_helm_parameters(self):
214         if not self.caas_utils.is_optional_param_present(self.HELM_PARAMETERS, self.caas_conf):
215             return
216         if not isinstance(self.caas_conf[self.HELM_PARAMETERS], dict):
217             raise CaasValidationError('The given input: {} is not a dictionary!'.format(
218                 self.caas_conf[self.HELM_PARAMETERS]))
219
220     def validate_encrypted_ca(self, enc_ca):
221         self.caas_utils.check_key_in_dict(enc_ca, self.caas_conf)
222         enc_ca_str = self.caas_conf[enc_ca][0]
223         if not enc_ca_str:
224             raise CaasValidationError('{} shall not be empty !'.format(enc_ca))
225         try:
226             base64.b64decode(enc_ca_str)
227         except TypeError as exc:
228             raise CaasValidationError('Invalid {}: {}'.format(enc_ca, exc))