Validation for new oam_cidr caas parameter
[ta/cm-plugins.git] / validators / src / CaasValidation.py
index e72a598..4bcee33 100644 (file)
@@ -84,6 +84,8 @@ class CaasValidation(cmvalidator.CMValidator):
 
     DOCKER0_CIDR = "docker0_cidr"
 
+    OAM_CIDR = "oam_cidr"
+
     INSTANTIATION_TIMEOUT = "instantiation_timeout"
 
     ENCRYPTED_CA = "encrypted_ca"
@@ -92,6 +94,17 @@ class CaasValidation(cmvalidator.CMValidator):
     CLUSTER_NETS = 'cluster_networks'
     TENANT_NETS = 'tenant_networks'
 
+    BLOG_FORWARDING = "infra_log_store"
+    LOG_FORWARDING = "log_forwarding"
+    URL_PORT_PATTERN = r"^(?:https?|udp|tcp):(?:\/\/)(?:((?:[\w\.-]+|" \
+                       r"\[(([1-9a-f][0-9a-f]{0,3}|\:)\:[1-9a-f][0-9a-f]{0,3}){0,7}\])\:[0-9]+))"
+    FLUENTD_PLUGINS = ['elasticsearch', 'remote_syslog']
+    INFRA_LOG_FLUENTD_PLUGINS = ['elasticsearch', 'remote_syslog']
+    LOG_FW_STREAM = ['stdout', 'stderr', 'both']
+
+    DOMAIN_NAME = "dns_domain"
+    DOMAIN_NAME_PATTERN = r"^[a-z0-9]([a-z0-9-\.]{0,253}[a-z0-9])?$"
+
     def __init__(self):
         cmvalidator.CMValidator.__init__(self)
         self.validation_utils = validation.ValidationUtils()
@@ -111,10 +124,13 @@ class CaasValidation(cmvalidator.CMValidator):
         self.validate_docker_size_quota()
         self.validate_helm_operation_timeout()
         self.validate_docker0_cidr(props)
+        self.validate_oam_cidr(props)
         self.validate_instantiation_timeout()
         self.validate_encrypted_ca(self.ENCRYPTED_CA)
         self.validate_encrypted_ca(self.ENCRYPTED_CA_KEY)
+        self.validate_log_forwarding()
         self.validate_networks(props)
+        self.validate_dns_domain()
 
     def _get_conf(self, props, domain):
         if props.get(domain):
@@ -156,28 +172,39 @@ class CaasValidation(cmvalidator.CMValidator):
                 '{}:{} is not an integer'.format(self.HELM_OP_TIMEOUT,
                                                  self.caas_conf[self.HELM_OP_TIMEOUT]))
 
-    def get_docker0_cidr_netw_obj(self, subnet):
+    def get_netw_obj(self, subnet, parameter):
         try:
             return ipaddr.IPNetwork(subnet)
         except ValueError as exc:
             raise CaasValidationError('{} is an invalid subnet address: {}'.format(
-                self.DOCKER0_CIDR, exc))
+                parameter, exc))
 
-    def check_docker0_cidr_overlaps_with_netw_subnets(self, docker0_cidr, props):
+    def check_cidr_overlaps_with_netw_subnets(self, cidr_in, props, parameter):
         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:
+        for cidr_in 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,
+                    'but it overlaps with {} from {}.'.format(parameter, cidr,
                                                               self.NETW_DOMAIN))
+    def check_oam_cidr_prefix(self, cidr_obj):
+        if ipaddr.IPNetwork(cidr_obj).prefixlen != 16:
+            raise CaasValidationError('Wrong subnet size in caas.oam_cidr parameter. '
+                                      'The currently supported subnet size is 16')
 
     def validate_docker0_cidr(self, props):
         if not self.caas_utils.is_optional_param_present(self.DOCKER0_CIDR, self.caas_conf):
             return
-        docker0_cidr_obj = self.get_docker0_cidr_netw_obj(self.caas_conf[self.DOCKER0_CIDR])
-        self.check_docker0_cidr_overlaps_with_netw_subnets(docker0_cidr_obj, props)
+        docker0_cidr_obj = self.get_netw_obj(self.caas_conf[self.DOCKER0_CIDR], self.DOCKER0_CIDR)
+        self.check_cidr_overlaps_with_netw_subnets(docker0_cidr_obj, props, self.DOCKER0_CIDR)
+
+    def validate_oam_cidr(self, props):
+        if not self.caas_utils.is_optional_param_present(self.OAM_CIDR, self.caas_conf):
+            return
+        oam_cidr_obj = self.get_netw_obj(self.caas_conf[self.OAM_CIDR], self.OAM_CIDR)
+        self.check_cidr_overlaps_with_netw_subnets(oam_cidr_obj, props, self.OAM_CIDR)
+        self.check_oam_cidr_prefix(oam_cidr_obj)
 
     def validate_instantiation_timeout(self):
         if not self.caas_utils.is_optional_param_present(self.INSTANTIATION_TIMEOUT,
@@ -197,6 +224,52 @@ class CaasValidation(cmvalidator.CMValidator):
         except TypeError as exc:
             raise CaasValidationError('Invalid {}: {}'.format(enc_ca, exc))
 
+    def validate_log_forwarding(self):
+        # pylint: disable=too-many-branches
+        if self.caas_utils.is_optional_param_present(self.BLOG_FORWARDING, self.caas_conf):
+            if self.caas_conf[self.BLOG_FORWARDING] not in self.INFRA_LOG_FLUENTD_PLUGINS:
+                raise CaasValidationError('"{}" property not valid! '
+                                          'Choose from {}!'.format(self.BLOG_FORWARDING,
+                                                                   self.INFRA_LOG_FLUENTD_PLUGINS))
+        if self.caas_utils.is_optional_param_present(self.LOG_FORWARDING, self.caas_conf):
+            log_fw_list = self.caas_conf[self.LOG_FORWARDING]
+            if log_fw_list:
+                url_d = dict()
+                url_s = set()
+                for list_item in log_fw_list:
+                    self.caas_utils.check_key_in_dict('namespace', list_item)
+                    if list_item['namespace'] == 'kube-system':
+                        raise CaasValidationError(
+                            'You can\'t set "kube-system" as namespace in "{}"!'.format(
+                                self.LOG_FORWARDING))
+                    self.caas_utils.check_key_in_dict('target_url', list_item)
+                    if not list_item['target_url'] or not re.match(self.URL_PORT_PATTERN,
+                                                                   list_item['target_url']):
+                        raise CaasValidationError(
+                            '"target_url" property {} not valid!'.format(list_item['target_url']))
+                    if not url_d:
+                        url_d[list_item['namespace']] = list_item['target_url']
+                    if list_item['namespace'] in url_d:
+                        if list_item['target_url'] in url_s:
+                            raise CaasValidationError('There can\'t be multiple rules for the same '
+                                                      'target_url for the same {} '
+                                                      'namespace!'.format(list_item['namespace']))
+                        else:
+                            url_s.add(list_item['target_url'])
+                        url_d[list_item['namespace']] = url_s
+                    else:
+                        url_d[list_item['namespace']] = list_item['target_url']
+                    if self.caas_utils.is_optional_param_present('plugin', list_item) and list_item[
+                        'plugin'] not in self.FLUENTD_PLUGINS:
+                        raise CaasValidationError(
+                            '"plugin" property not valid! Choose from {}'.format(
+                                self.FLUENTD_PLUGINS))
+                    if self.caas_utils.is_optional_param_present('stream', list_item) and list_item[
+                        'stream'] not in self.LOG_FW_STREAM:
+                        raise CaasValidationError(
+                            '"stream" property not valid! Choose from {}'.format(
+                                self.LOG_FW_STREAM))
+
     def validate_networks(self, props):
         caas_nets = []
         for nets_key in [self.CLUSTER_NETS, self.TENANT_NETS]:
@@ -226,25 +299,80 @@ class CaasValidation(cmvalidator.CMValidator):
                         ('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))
+                        caas_provider_interfaces = self._filter_provider_networks_by_type(
+                            self._filter_provider_networkinterfaces_by_net(ifaces, net), 'caas')
+                        sriov_networks = net_prof.get('sriov_provider_networks', {})
+                        caas_sriov_networks_present = bool(
+                            net in sriov_networks and
+                            sriov_networks[net].get('type', "") == 'caas')
+                        if not caas_provider_interfaces and not caas_sriov_networks_present:
+                            raise CaasValidationError('CaaS network {} missing from host {}'
+                                                      .format(net, host))
+                        if caas_provider_interfaces:
+                            self._validate_homogenous_provider_net_setup(
+                                net_iface_map, net, ifaces)
+                        if caas_sriov_networks_present:
+                            self._validate_homogenous_sriov_provider_net_setup(
+                                net_iface_map, net, sriov_networks)
+
+    @staticmethod
+    def _filter_provider_networks_by_type(profile, net_type):
+        return {name: network for name, network in profile.iteritems()
+                if network.get('type', "") == net_type}
+
+    @staticmethod
+    def _filter_provider_networkinterfaces_by_net(provider_interfaces, provider_net):
+        return {iface: data for iface, data in provider_interfaces.iteritems()
+                if provider_net in data.get('provider_networks', [])}
+
+    @staticmethod
+    def _validate_homogenous_provider_net_setup(net_iface_map, net, ifaces):
+        is_caas_network_present = False
+        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
+        return is_caas_network_present
+
+    @staticmethod
+    def _validate_homogenous_sriov_provider_net_setup(net_iface_map, net, sriov_networks):
+        is_caas_network_present = False
+        sriov_provider_net = sriov_networks.get(net, {})
+        if sriov_provider_net and sriov_provider_net.get('type') == 'caas':
+            interfaces = sriov_provider_net.get('interfaces', [])
+            tenant_interfaces = net_iface_map.get(net)
+            already_used_ifaces = [set(x).intersection(interfaces)
+                                   for x in net_iface_map.itervalues()
+                                   if x and isinstance(x, list)]
+            if tenant_interfaces is None and interfaces:
+                net_iface_map[net] = interfaces
+                is_caas_network_present = True
+            elif already_used_ifaces:
+                msg = 'CaaS network {} mapped to sriov interfaces {} in one host '
+                msg += 'and sriov interfaces {} in another host'
+                raise CaasValidationError(msg.format(net, interfaces,
+                                                     tenant_interfaces))
+        return is_caas_network_present
+
+    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))