Add CaaS networks related validations
[ta/cm-plugins.git] / validators / src / CaasValidation.py
index 9bb0b8c..e72a598 100644 (file)
@@ -67,18 +67,18 @@ class CaasValidationUtils(object):
 
 
 class CaasValidation(cmvalidator.CMValidator):
-
-    SUBSCRIPTION = r'^cloud\.caas|cloud\.hosts|cloud\.networking$'
+    SUBSCRIPTION = r'^cloud\.caas|cloud\.hosts|cloud\.networking|cloud\.network_profiles$'
     CAAS_DOMAIN = 'cloud.caas'
-    NETW_DOMAIN = 'cloud.networking'
     HOSTS_DOMAIN = 'cloud.hosts'
+    NETW_DOMAIN = 'cloud.networking'
+    NETPROF_DOMAIN = 'cloud.network_profiles'
 
     SERV_PROF = 'service_profiles'
     CAAS_PROFILE_PATTERN = 'caas_master|caas_worker'
     CIDR = 'cidr'
 
-    DOCKER_SIZE_QOUTA = "docker_size_quota"
-    DOCKER_SIZE_QOUTA_PATTERN = r"^\d*[G,M,K]$"
+    DOCKER_SIZE_QUOTA = "docker_size_quota"
+    DOCKER_SIZE_QUOTA_PATTERN = r"^\d*[G,M,K]$"
 
     HELM_OP_TIMEOUT = "helm_operation_timeout"
 
@@ -89,8 +89,8 @@ class CaasValidation(cmvalidator.CMValidator):
     ENCRYPTED_CA = "encrypted_ca"
     ENCRYPTED_CA_KEY = "encrypted_ca_key"
 
-    DOMAIN_NAME = "dns_domain"
-    DOMAIN_NAME_PATTERN = "^[a-z0-9]([a-z0-9-\.]{0,253}[a-z0-9])?$"
+    CLUSTER_NETS = 'cluster_networks'
+    TENANT_NETS = 'tenant_networks'
 
     def __init__(self):
         cmvalidator.CMValidator.__init__(self)
@@ -114,10 +114,19 @@ class CaasValidation(cmvalidator.CMValidator):
         self.validate_instantiation_timeout()
         self.validate_encrypted_ca(self.ENCRYPTED_CA)
         self.validate_encrypted_ca(self.ENCRYPTED_CA_KEY)
-        self.validate_dns_domain()
+        self.validate_networks(props)
+
+    def _get_conf(self, props, domain):
+        if props.get(domain):
+            conf_str = props[domain]
+        else:
+            conf_str = self.get_plugin_client().get_property(domain)
+        return json.loads(conf_str)
 
     def is_caas_mandatory(self, props):
-        hosts_conf = json.loads(props[self.HOSTS_DOMAIN])
+        if not isinstance(props, dict):
+            raise CaasValidationError('The given input: {} is not a dictionary!'.format(props))
+        hosts_conf = self._get_conf(props, self.HOSTS_DOMAIN)
         service_profiles = self.caas_utils.get_every_key_occurrence(hosts_conf, self.SERV_PROF)
         pattern = re.compile(self.CAAS_PROFILE_PATTERN)
         for profile in service_profiles:
@@ -126,31 +135,26 @@ class CaasValidation(cmvalidator.CMValidator):
         return False
 
     def props_pre_check(self, props):
-        if not isinstance(props, dict):
-            raise CaasValidationError('The given input: {} is not a dictionary!'.format(props))
-        if self.CAAS_DOMAIN not in props:
-            raise CaasValidationError(
-                '{} configuration is missing from {}!'.format(self.CAAS_DOMAIN, props))
-        self.caas_conf = json.loads(props[self.CAAS_DOMAIN])
+        self.caas_conf = self._get_conf(props, self.CAAS_DOMAIN)
         self.conf = {self.CAAS_DOMAIN: self.caas_conf}
         if not self.caas_conf:
             raise CaasValidationError('{} is an empty dictionary!'.format(self.conf))
 
     def validate_docker_size_quota(self):
-        if not self.caas_utils.is_optional_param_present(self.DOCKER_SIZE_QOUTA, self.caas_conf):
+        if not self.caas_utils.is_optional_param_present(self.DOCKER_SIZE_QUOTA, self.caas_conf):
             return
-        if not re.match(self.DOCKER_SIZE_QOUTA_PATTERN, self.caas_conf[self.DOCKER_SIZE_QOUTA]):
-            raise CaasValidationError('{} is not a valid {}!'.format(
-                self.caas_conf[self.DOCKER_SIZE_QOUTA],
-                self.DOCKER_SIZE_QOUTA))
+        if not re.match(self.DOCKER_SIZE_QUOTA_PATTERN, self.caas_conf[self.DOCKER_SIZE_QUOTA]):
+            raise CaasValidationError(
+                '{} is not a valid {}!'.format(self.caas_conf[self.DOCKER_SIZE_QUOTA],
+                                               self.DOCKER_SIZE_QUOTA))
 
     def validate_helm_operation_timeout(self):
         if not self.caas_utils.is_optional_param_present(self.HELM_OP_TIMEOUT, self.caas_conf):
             return
         if not isinstance(self.caas_conf[self.HELM_OP_TIMEOUT], int):
-            raise CaasValidationError('{}:{} is not an integer'.format(
-                self.HELM_OP_TIMEOUT,
-                self.caas_conf[self.HELM_OP_TIMEOUT]))
+            raise CaasValidationError(
+                '{}:{} is not an integer'.format(self.HELM_OP_TIMEOUT,
+                                                 self.caas_conf[self.HELM_OP_TIMEOUT]))
 
     def get_docker0_cidr_netw_obj(self, subnet):
         try:
@@ -160,14 +164,14 @@ class CaasValidation(cmvalidator.CMValidator):
                 self.DOCKER0_CIDR, exc))
 
     def check_docker0_cidr_overlaps_with_netw_subnets(self, docker0_cidr, props):
-        netw_conf = json.loads(props[self.NETW_DOMAIN])
+        netw_conf = self._get_conf(props, self.NETW_DOMAIN)
         cidrs = self.caas_utils.get_every_key_occurrence(netw_conf, self.CIDR)
         for cidr in cidrs:
             if docker0_cidr.overlaps(ipaddr.IPNetwork(cidr)):
                 raise CaasValidationError(
                     'CIDR configured for {} shall be an unused IP range, '
-                    'but it overlaps with {} from {}.'.format(
-                        self.DOCKER0_CIDR, cidr, self.NETW_DOMAIN))
+                    'but it overlaps with {} from {}.'.format(self.DOCKER0_CIDR, cidr,
+                                                              self.NETW_DOMAIN))
 
     def validate_docker0_cidr(self, props):
         if not self.caas_utils.is_optional_param_present(self.DOCKER0_CIDR, self.caas_conf):
@@ -181,8 +185,7 @@ class CaasValidation(cmvalidator.CMValidator):
             return
         if not isinstance(self.caas_conf[self.INSTANTIATION_TIMEOUT], int):
             raise CaasValidationError('{}:{} is not an integer'.format(
-                self.INSTANTIATION_TIMEOUT,
-                self.caas_conf[self.INSTANTIATION_TIMEOUT]))
+                self.INSTANTIATION_TIMEOUT, self.caas_conf[self.INSTANTIATION_TIMEOUT]))
 
     def validate_encrypted_ca(self, enc_ca):
         self.caas_utils.check_key_in_dict(enc_ca, self.caas_conf)
@@ -194,11 +197,54 @@ class CaasValidation(cmvalidator.CMValidator):
         except TypeError as exc:
             raise CaasValidationError('Invalid {}: {}'.format(enc_ca, exc))
 
-    def validate_dns_domain(self):
-        domain = self.caas_conf[self.DOMAIN_NAME]
-        if not self.caas_utils.is_optional_param_present(self.DOMAIN_NAME, self.caas_conf):
-            return
-        if not re.match(self.DOMAIN_NAME_PATTERN, domain):
-            raise CaasValidationError('{} is not a valid {} !'.format(
-                domain,
-                self.DOMAIN_NAME))
+    def validate_networks(self, props):
+        caas_nets = []
+        for nets_key in [self.CLUSTER_NETS, self.TENANT_NETS]:
+            if self.caas_utils.is_optional_param_present(nets_key, self.caas_conf):
+                if not isinstance(self.caas_conf[nets_key], list):
+                    raise CaasValidationError('{} is not a list'.format(nets_key))
+                if len(set(self.caas_conf[nets_key])) != len(self.caas_conf[nets_key]):
+                    raise CaasValidationError('{} has duplicate entries'.format(nets_key))
+                caas_nets.extend(self.caas_conf[nets_key])
+        if len(set(caas_nets)) != len(caas_nets):
+            raise CaasValidationError('{} and {} must be distinct, but same entries are '
+                                      'found from both lists'.format(self.CLUSTER_NETS,
+                                                                     self.TENANT_NETS))
+        self._validate_homogenous_net_setup(props, caas_nets)
+
+    def _validate_homogenous_net_setup(self, props, caas_nets):
+        # Validate homogenous CaaS provider network setup
+        # pylint: disable=too-many-locals,too-many-nested-blocks
+        hosts_conf = self._get_conf(props, self.HOSTS_DOMAIN)
+        netprof_conf = self._get_conf(props, self.NETPROF_DOMAIN)
+        net_iface_map = {}
+        for net in caas_nets:
+            net_iface_map[net] = None
+            for host, host_conf in hosts_conf.iteritems():
+                # Validate only nodes that can host containerized workloads
+                if ('caas_worker' in host_conf[self.SERV_PROF] or
+                        ('caas_master' in host_conf[self.SERV_PROF] and
+                         'compute' not in host_conf[self.SERV_PROF])):
+                    # Validating CaaS network 'net' mapping in 'host'
+                    is_caas_network_present = False
+                    profiles = host_conf.get('network_profiles')
+                    if isinstance(profiles, list) and profiles:
+                        net_prof = netprof_conf.get(profiles[0])
+                    if net_prof is not None:
+                        ifaces = net_prof.get('provider_network_interfaces', {})
+                        for iface, data in ifaces.iteritems():
+                            net_type = data.get('type')
+                            networks = data.get('provider_networks', [])
+                            if net in networks and net_type == 'caas':
+                                is_caas_network_present = True
+                                if net_iface_map[net] is None:
+                                    net_iface_map[net] = iface
+                                elif net_iface_map[net] != iface:
+                                    msg = 'CaaS network {} mapped to interface {} in one host '
+                                    msg += 'and interface {} in another host'
+                                    raise CaasValidationError(msg.format(net, iface,
+                                                                         net_iface_map[net]))
+                                break
+                    if not is_caas_network_present:
+                        raise CaasValidationError('CaaS network {} missing from host {}'
+                                                  .format(net, host))