Merge "openstack storage backend check removed"
authorLevente Kálé <levente.kale@nokia.com>
Tue, 18 Jun 2019 14:01:50 +0000 (14:01 +0000)
committerGerrit Code Review <gerrit@akraino.org>
Tue, 18 Jun 2019 14:01:50 +0000 (14:01 +0000)
inventoryhandlers/baremetal-node-inventory/zbaremetalnodeinventory.py [new file with mode: 0644]
recuserconfighandlers.spec [new file with mode: 0644]
recuserconfighandlers/recnetworkprofileshandler/recnetworkprofileshandler.py [new file with mode: 0644]
validators/src/CaasValidation.py
validators/src/NetworkProfilesValidation.py
validators/src/VersionValidation.py

diff --git a/inventoryhandlers/baremetal-node-inventory/zbaremetalnodeinventory.py b/inventoryhandlers/baremetal-node-inventory/zbaremetalnodeinventory.py
new file mode 100644 (file)
index 0000000..249a5c0
--- /dev/null
@@ -0,0 +1,332 @@
+# Copyright 2019 Nokia
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+from jinja2 import Environment
+
+from cmframework.apis import cmansibleinventoryconfig
+from cmdatahandlers.api import utils
+
+
+nics_json_txt = """
+[ {%- if 'mgmt_mac' in all_vars['hosts'][host] %}
+      {%- for mac_members in all_vars['hosts'][host]['mgmt_mac'] %}
+          {
+          "mac": "{{ mac_members }}"
+          }
+          {%- if not loop.last %},{%- endif %}
+      {%- endfor %}
+  {%- else: %}
+      {
+      "mac": "{{ all_vars['hw_inventory_details'][host]['mgmt_mac'] }}"
+      }
+  {%- endif %}
+]
+"""
+
+
+class zbaremetalnodeinventory(cmansibleinventoryconfig.CMAnsibleInventoryConfigPlugin):
+    def __init__(self, confman, inventory, ownhost):
+        super(zbaremetalnodeinventory, self).__init__(confman, inventory, ownhost)
+
+    def handle_bootstrapping(self):
+        pass
+
+    def handle_provisioning(self):
+        self.handle()
+
+    def handle_postconfig(self):
+        self.handle()
+
+    def handle_setup(self):
+        pass
+
+    @staticmethod
+    def _check_host_single_nic(host_network_profile_value, host_interface_net_mapping):
+        if 'provider_network_interfaces' in host_network_profile_value:
+            host_provider_network_interfaces = host_network_profile_value['provider_network_interfaces']
+            if len(host_interface_net_mapping) == 1 and len(host_provider_network_interfaces) == 1:
+                if host_interface_net_mapping.keys()[0] == host_provider_network_interfaces.keys()[0]:
+                    return True
+        return False
+
+    @staticmethod
+    def _generate_linux_bonding_options(options):
+        mode_mapping = {'active-backup': 'active-backup', 'lacp': '802.3ad'}
+        default_options = {'active-backup': 'miimon=100',
+                           'lacp': 'lacp_rate=fast miimon=100'}
+        for i in options.split():
+            key, value = i.split('=')
+            if key == 'mode':
+                if default_options[value]:
+                    return 'mode=' + mode_mapping[value] + ' ' + default_options[value]
+                return 'mode=' + mode_mapping[value]
+
+    @staticmethod
+    def _generate_ovs_bonding_options(options):
+        mode_mapping = {'active-backup': 'active-backup', 'lacp': 'balance-slb',
+                        'lacp-layer34': 'balance-tcp'}
+        default_options = {'active-backup': '',
+                           'lacp': 'lacp=active other_config:lacp-time=fast other_config:bond-detect-mode=carrier',
+                           'lacp-layer34': 'lacp=active other_config:lacp-time=fast other_config:bond-detect-mode=carrier'}
+        for i in options.split():
+            key, value = i.split('=')
+            if key == 'mode':
+                if default_options[value]:
+                    return 'bond_mode=' + mode_mapping[value] + ' ' + default_options[value]
+                return 'bond_mode=' + mode_mapping[value]
+
+    @staticmethod
+    def _add_static_routes(routes):
+        routes_list = []
+        for route in routes:
+            routes_list.append({"ip_netmask": route["to"], "next_hop": route["via"]})
+        return routes_list
+
+    def handle(self):
+        usersconf = self.confman.get_users_config_handler()
+        admin_user = usersconf.get_admin_user()
+        self.add_global_var("home_dir", "/home/" + admin_user)
+        all_vars = self.inventory['all']['vars']
+        host_locals = self.inventory['_meta']['hostvars']
+        nfs_server_ip = host_locals[all_vars['installation_controller']]['networking']['infra_external']['ip']
+
+        for host, hostvars in host_locals.iteritems():
+            host_hdd_mapping = hostvars['by_path_disks']
+            host_networking = hostvars['networking']
+            host_network_profiles_list = all_vars['hosts'][host]['network_profiles']
+            host_network_profile_value = all_vars['network_profiles'][host_network_profiles_list[0]]
+            host_interface_net_mapping = host_network_profile_value['interface_net_mapping']
+
+            infra_bond = {'in_use': False}
+            host_bonding_interfaces = host_network_profile_value.get('bonding_interfaces', {})
+            default_mtu = all_vars['networking'].get('mtu', 1500)
+
+            sriov_mtus = {}
+            if 'sriov_provider_networks' in host_network_profile_value:
+                sriov_nets = host_network_profile_value['sriov_provider_networks']
+                prov_infos = host_networking.get('provider_networks', {})
+                for net_name, sriov_info in sriov_nets.iteritems():
+                    if prov_infos.get(net_name):
+                        prov_info = prov_infos[net_name]
+                        sriov_mtu = prov_info.get('mtu', default_mtu)
+                        for iface in sriov_info['interfaces']:
+                            sriov_mtus[iface] = sriov_mtu
+
+            mtu = default_mtu
+            if 'mtu' in all_vars['networking']['infra_internal']:
+                mtu = all_vars['networking']['infra_internal']['mtu']
+
+            phys_iface_mtu = 1500
+            if 'vlan' in host_networking['infra_internal']:
+                for iface, infras in host_interface_net_mapping.iteritems():
+                    if 'infra_internal' in infras:
+                        for infra in infras:
+                            tmp_mtu = default_mtu
+                            if 'mtu' in all_vars['networking'][infra]:
+                                tmp_mtu = all_vars['networking'][infra]['mtu']
+                            if infra == 'cloud_tenant':
+                                tmp_mtu = tmp_mtu + 50
+                            if tmp_mtu > phys_iface_mtu:
+                                phys_iface_mtu = tmp_mtu
+                        if 'bond' in iface:
+                            if host_bonding_interfaces.get(iface):
+                                for slave in host_bonding_interfaces[iface]:
+                                    if slave in sriov_mtus and sriov_mtus[slave] > phys_iface_mtu:
+                                        phys_iface_mtu = sriov_mtus[slave]
+                        elif iface in sriov_mtus and sriov_mtus[iface] > phys_iface_mtu:
+                            phys_iface_mtu = sriov_mtus[iface]
+                        break
+
+            properties = {
+                "capabilities": "boot_option:local",
+                "cpu_arch": "x86_64",
+                "cpus": 8,
+                "disk_size": 40,
+                "ram": 16384
+            }
+
+            power = {
+                "provisioning_server": nfs_server_ip,
+                "virtmedia_deploy_iso": "file:///opt/images/ironic-deploy.iso",
+            }
+
+            if utils.is_virtualized():
+                driver = "ssh_virtmedia"
+                properties["root_device"] = {"by_path": host_hdd_mapping['os']}
+                power["ssh_address"] = all_vars['hosts'][host]['hwmgmt']['address']
+                power["ssh_username"] = all_vars['hosts'][host]['hwmgmt']['user']
+                power["ipmi_port"] = all_vars['hosts'][host]['vbmc_port']
+                power["ipmi_username"] = "admin"
+                power["ipmi_password"] = "password"
+                power["ssh_key_contents"] = "{{ lookup('file', '/etc/userconfig/id_rsa') }}"
+                power["ipmi_address"] = host_locals[all_vars['installation_controller']]['networking']['infra_internal']['ip']
+            else:
+                driver = "ipmi_virtmedia"
+                power["ipmi_address"] = all_vars['hosts'][host]['hwmgmt']['address']
+                power["ipmi_password"] = all_vars['hosts'][host]['hwmgmt']['password']
+                power["ipmi_username"] = all_vars['hosts'][host]['hwmgmt']['user']
+                power["product_family"] = all_vars['hw_inventory_details'][host]['product_family']
+                power["vendor"] = all_vars['hw_inventory_details'][host]['vendor']
+
+                if host_hdd_mapping['os'] != "/dev/sda":
+                    properties["root_device"] = {"by_path": host_hdd_mapping['os']}
+                else:
+                    properties["root_device"] = {"name": host_hdd_mapping['os']}
+
+            nics_text = Environment().from_string(nics_json_txt).render(all_vars=all_vars, host=host)
+            nics_inventory = json.loads(nics_text)
+
+            driver_info = {}
+            driver_info["power"] = power
+            #####################################################
+            network_config = []
+            if 'interface' in host_networking['infra_internal']:
+                if not self._check_host_single_nic(host_network_profile_value, host_interface_net_mapping):
+                    if 'bonding_interfaces' in host_network_profile_value:
+                        for net_key, net_value in host_interface_net_mapping.iteritems():
+                            bond_contents = {}
+                            if "bond" in net_key and "infra_internal" in net_value:
+                                members = []
+                                for member in host_bonding_interfaces[net_key]:
+                                    member_element = {}
+                                    if 'bond' in host_networking['infra_internal']['interface']:
+                                        member_element["mtu"] = mtu
+                                    else:
+                                        member_element["mtu"] = phys_iface_mtu
+                                    member_element["name"] = member
+                                    member_element["type"] = "interface"
+                                    member_element["use_dhcp"] = False
+                                    members.append(member_element)
+
+                                bond_contents = {
+                                    "type": "linux_bond",
+                                    "use_dhcp": False
+                                }
+                                bond_contents["name"] = net_key
+                                bond_contents["members"] = members
+
+                                if 'linux_bonding_options' in host_network_profile_value:
+                                    bond_contents["bonding_options"] = self._generate_linux_bonding_options(host_network_profile_value['linux_bonding_options'])
+                                if 'bond' in host_networking['infra_internal']['interface']:
+                                    bond_contents["addresses"] = [{"ip_netmask": "%s/%s" % (host_networking['infra_internal']['ip'], host_networking['infra_internal']['mask'])}]
+                                    bond_contents["mtu"] = mtu
+                                    if 'routes' in host_networking['infra_internal']:
+                                        routes = host_networking['infra_internal']['routes']
+                                        bond_contents["routes"] = self._add_static_routes(routes)
+                                else:
+                                    bond_contents["mtu"] = phys_iface_mtu
+
+                                infra_bond.update({'in_use': True})
+
+                                network_config.append(bond_contents)
+                    if 'vlan' in host_networking['infra_internal']:
+                        vlan_contents = {
+                            "type": "vlan",
+                            "use_dhcp": False
+                            }
+                        vlan_contents["addresses"] = [{"ip_netmask": "%s/%s" % (host_networking['infra_internal']['ip'], host_networking['infra_internal']['mask'])}]
+                        vlan_contents["vlan_id"] = host_networking['infra_internal']['vlan']
+                        for net_key, net_value in host_interface_net_mapping.iteritems():
+                            if "infra_internal" in net_value:
+                                vlan_contents["device"] = net_key
+                        vlan_contents["mtu"] = mtu
+                        if 'routes' in host_networking['infra_internal']:
+                            routes = host_networking['infra_internal']['routes']
+                            vlan_contents["routes"] = []
+                            for route in routes:
+                                vlan_contents["routes"].append({"ip_netmask": route["to"], "next_hop": route["via"]})
+                        if not infra_bond["in_use"]:
+                            vlan_phy_contents = {
+                                "type": "interface",
+                                "use_dhcp": False,
+                                "mtu": phys_iface_mtu
+                                }
+                            for net_key, net_value in host_interface_net_mapping.iteritems():
+                                if "infra_internal" in net_value:
+                                    vlan_phy_contents["name"] = net_key
+                            network_config.append(vlan_phy_contents)
+
+                        network_config.append(vlan_contents)
+
+                    elif not infra_bond["in_use"]:
+                        phy_contents = {
+                            "name": host_networking['infra_internal']['interface'],
+                            "type": "interface",
+                            "mtu": mtu,
+                            "use_dhcp": False
+                            }
+                        phy_contents["addresses"] = [{"ip_netmask": "%s/%s" % (host_networking['infra_internal']['ip'], host_networking['infra_internal']['mask'])}]
+                        if 'routes' in host_networking['infra_internal']:
+                            routes = host_networking['infra_internal']['routes']
+                            phy_contents["routes"] = self._add_static_routes(routes)
+
+                        network_config.append(phy_contents)
+
+                # --> single_nic_setup <-- #
+                else:
+                    single_nic_contents = {
+                        "name": "br-pro0",
+                        "type": "ovs_bridge",
+                        "members": []
+                        }
+                    member_elements = {"mtu": phys_iface_mtu, "use_dhcp": False}
+                    iface = host_interface_net_mapping.keys()[0]
+                    if 'bond' in iface:
+                        for bond_iface, bond_value in host_bonding_interfaces.iteritems():
+                            if bond_iface == iface:
+                                if 'ovs_bonding_options' in host_network_profile_value:
+                                    member_elements["ovs_options"] = self._generate_ovs_bonding_options(host_network_profile_value['ovs_bonding_options'])
+                                member_elements["name"] = iface
+                                member_elements["type"] = "ovs_bond"
+                                member_elements["members"] = []
+                                for member in bond_value:
+                                    ovs_bond_member = {
+                                        "name": member,
+                                        "type": "interface",
+                                        "mtu": phys_iface_mtu,
+                                        "use_dhcp": False
+                                        }
+                                    member_elements["members"].append(ovs_bond_member)
+                            single_nic_contents["members"].append(member_elements)
+                    else:
+                        member_elements["name"] = iface
+                        member_elements["type"] = "interface"
+                        single_nic_contents["members"].append(member_elements)
+
+                    infra_elements = {}
+                    infra = host_networking['infra_internal']
+                    infra_elements["use_dhcp"] = False
+                    infra_elements["type"] = "vlan"
+                    infra_elements["vlan_id"] = infra['vlan']
+                    infra_elements["mtu"] = mtu
+                    infra_elements["addresses"] = [{"ip_netmask": "%s/%s" % (infra['ip'], infra['mask'])}]
+                    if 'routes' in infra:
+                        routes = infra['routes']
+                        infra_elements["routes"] = self._add_static_routes(routes)
+
+                    single_nic_contents["members"].append(infra_elements)
+                    network_config.append(single_nic_contents)
+            #####################################################
+            driver_info["power"]["os_net_config"] = {"network_config": network_config}
+
+            ironic_node_details = {
+                "name": host,
+                "driver": driver,
+                "network_interface": "noop",
+                "nics": nics_inventory,
+                "properties": properties,
+                "driver_info": driver_info
+            }
+            self.add_host_var(host, 'ironic_node_details', ironic_node_details)
diff --git a/recuserconfighandlers.spec b/recuserconfighandlers.spec
new file mode 100644 (file)
index 0000000..c70b09a
--- /dev/null
@@ -0,0 +1,50 @@
+# Copyright 2019 Nokia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Name:       recuserconfighandlers
+Version:    %{_version}
+Release:    1%{?dist}
+Summary:    REC user configuration handlers
+License:    %{_platform_licence}
+Source0:    %{name}-%{version}.tar.gz
+Vendor:     %{_platform_vendor}
+
+BuildArch:      noarch
+
+%define PKG_BASE_DIR /opt/cmframework/userconfighandlers
+
+%description
+REC user configuration handlers
+
+
+%prep
+%autosetup
+
+%build
+
+%install
+mkdir -p %{buildroot}/%{PKG_BASE_DIR}/
+find recuserconfighandlers -name '*.py' -exec cp {} %{buildroot}/%{PKG_BASE_DIR}/ \;
+
+%files
+%defattr(0755,root,root,0755)
+%{PKG_BASE_DIR}/*.py*
+
+%preun
+
+
+%postun
+
+%clean
+rm -rf ${buildroot}
diff --git a/recuserconfighandlers/recnetworkprofileshandler/recnetworkprofileshandler.py b/recuserconfighandlers/recnetworkprofileshandler/recnetworkprofileshandler.py
new file mode 100644 (file)
index 0000000..9bcbf4f
--- /dev/null
@@ -0,0 +1,64 @@
+#! /usr/bin/python
+# Copyright 2019 Nokia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from cmframework.apis import cmuserconfig
+from cmframework.apis import cmerror
+from cmdatahandlers.api import configerror
+from cmdatahandlers.network_profiles.config import config as net_prof_config
+
+"""
+This plugin is used to handle REC specific network profiles configs. Currently
+its sole purpuse is to set default type for any provider networks.
+"""
+
+
+class recnetworkprofileshandler(cmuserconfig.CMUserConfigPlugin):
+    defaul_sriov_net_type = 'caas'
+    defaul_provider_interface_type = 'caas'
+
+    def __init__(self):
+        super(recnetworkprofileshandler, self).__init__()
+
+    def handle(self, confman):
+        try:
+            self._set_default_type_for_provider_networks(confman)
+        except configerror.ConfigError as exp:
+            raise cmerror.CMError(str(exp))
+
+    def _set_default_type_for_provider_networks(self, confman):
+        netprofconf = confman.get_network_profiles_config_handler()
+        network_profiles = netprofconf.get_network_profiles()
+        for profile in network_profiles:
+            try:
+                self._set_default_provider_network_type(netprofconf, profile)
+                self._set_default_sriov_provider_network_type(netprofconf, profile)
+            except (net_prof_config.MissingProviderNetworkInterfaces, net_prof_config.MissingSriovProviderNetworks):
+                pass
+
+    def _set_default_provider_network_type(self, netprofconf, profile):
+        for interface in netprofconf.get_profile_provider_network_interfaces(profile):
+            try:
+                netprofconf.get_profile_provider_network_interface_type(
+                    profile, interface)
+            except net_prof_config.MissingProviderNetworkInterfaceType:
+                netprofconf.set_profile_provider_network_interface_type(
+                        profile, interface, self.defaul_provider_interface_type)
+
+    def _set_default_sriov_provider_network_type(self, netprofconf, profile):
+        for sriov_net in netprofconf.get_profile_sriov_provider_networks(profile):
+            if not netprofconf.get_profile_sriov_provider_network_type(
+                    profile, sriov_net):
+                netprofconf.set_profile_sriov_provider_network_type(
+                        profile, sriov_net, self.defaul_sriov_net_type)
index a42ef67..e72a598 100644 (file)
@@ -67,24 +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]$"
-
-    CHART_NAME = "chart_name"
-    CHART_NAME_PATTERN = r"[A-Za-z0-9\.-_]+"
-
-    CHART_VERSION = "chart_version"
-    CHART_VERSION_PATTERN = r"^\d+\.\d+\.\d+$"
+    DOCKER_SIZE_QUOTA = "docker_size_quota"
+    DOCKER_SIZE_QUOTA_PATTERN = r"^\d*[G,M,K]$"
 
     HELM_OP_TIMEOUT = "helm_operation_timeout"
 
@@ -92,13 +86,11 @@ class CaasValidation(cmvalidator.CMValidator):
 
     INSTANTIATION_TIMEOUT = "instantiation_timeout"
 
-    HELM_PARAMETERS = "helm_parameters"
-
     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)
@@ -117,18 +109,24 @@ class CaasValidation(cmvalidator.CMValidator):
             return
         self.props_pre_check(props)
         self.validate_docker_size_quota()
-        self.validate_chart_name()
-        self.validate_chart_version()
         self.validate_helm_operation_timeout()
         self.validate_docker0_cidr(props)
         self.validate_instantiation_timeout()
-        self.validate_helm_parameters()
         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:
@@ -137,50 +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):
-            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))
-
-    def validate_chart_name(self):
-        if not self.caas_utils.is_optional_param_present(self.CHART_NAME, 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.CHART_NAME_PATTERN, self.caas_conf[self.CHART_NAME]):
-            raise CaasValidationError('{} is not a valid {} !'.format(
-                self.caas_conf[self.CHART_NAME],
-                self.CHART_NAME))
-
-    def validate_chart_version(self):
-        if not self.caas_utils.is_optional_param_present(self.CHART_VERSION, self.caas_conf):
-            return
-        if not self.caas_conf[self.CHART_NAME]:
-            logging.warn('{} shall be set only, when {} is set.'.format(
-                self.CHART_VERSION, self.CHART_NAME))
-        if not re.match(self.CHART_VERSION_PATTERN, self.caas_conf[self.CHART_VERSION]):
-            raise CaasValidationError('{} is not a valid {} !'.format(
-                self.caas_conf[self.CHART_VERSION],
-                self.CHART_VERSION))
+        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:
@@ -190,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):
@@ -211,15 +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]))
-
-    def validate_helm_parameters(self):
-        if not self.caas_utils.is_optional_param_present(self.HELM_PARAMETERS, self.caas_conf):
-            return
-        if not isinstance(self.caas_conf[self.HELM_PARAMETERS], dict):
-            raise CaasValidationError('The given input: {} is not a dictionary!'.format(
-                self.caas_conf[self.HELM_PARAMETERS]))
+                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)
@@ -231,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))
index 875d5cb..ae8ad1a 100644 (file)
@@ -43,11 +43,19 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
     LINUX_BONDING_OPTIONS = 'linux_bonding_options'
     OVS_BONDING_OPTIONS = 'ovs_bonding_options'
 
+    TYPE_CAAS = 'caas'
+    TYPE_OPENSTACK = 'openstack'
     TYPE_OVS = 'ovs'
     TYPE_OVS_DPDK = 'ovs-dpdk'
     TYPE_OVS_OFFLOAD_SRIOV = "ovs-offload-sriov"
     TYPE_OVS_OFFLOAD_VIRTIO = "ovs-offload-virtio"
-    VALID_TYPES = [TYPE_OVS, TYPE_OVS_DPDK, TYPE_OVS_OFFLOAD_SRIOV, TYPE_OVS_OFFLOAD_VIRTIO]
+    VALID_PROVIDER_TYPES = [TYPE_CAAS,
+                            TYPE_OVS,
+                            TYPE_OVS_DPDK,
+                            TYPE_OVS_OFFLOAD_SRIOV,
+                            TYPE_OVS_OFFLOAD_VIRTIO]
+    SINGLE_NIC_UNSUPPORTED_TYPES = [TYPE_CAAS, TYPE_OVS_DPDK]
+    VALID_SRIOV_TYPES = [TYPE_CAAS, TYPE_OPENSTACK]
 
     MODE_LACP = 'mode=lacp'
     MODE_LACP_LAYER34 = 'mode=lacp-layer34'
@@ -82,8 +90,13 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
     ERR_UNTAGGED_MTU_SIZE = 'Untagged network {1} in {0} has too small MTU, ' + \
                             'VLAN tagged networks with bigger MTU exists on the same interface'
 
-    ERR_INVALID_TYPE = 'Invalid provider network type for interface {}, valid types: %s' % \
-        VALID_TYPES
+    ERR_INVALID_PROVIDER_TYPE = \
+        'Invalid provider network type for interface {}, valid types: %s' % \
+        VALID_PROVIDER_TYPES
+    ERR_INVALID_SRIOV_TYPE = \
+        'Invalid sr-iov network type for network {}, valid types: %s' % \
+        VALID_SRIOV_TYPES
+
     ERR_DPDK_MAX_RX_QUEUES = 'Invalid %s value {}, must be positive integer' % DPDK_MAX_RX_QUEUES
     ERR_MISSPLACED_MTU = 'Missplaced MTU inside %s interface {}' % PROVIDER_NETWORK_INTERFACES
     ERR_OVS_TYPE_CONFLICT = 'Cannot have both %s and %s types of provider networks in {}' % \
@@ -110,11 +123,11 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
     ERR_SRIOV_PROVIDER_VLAN_CONFLICT = \
         'SR-IOV network {} vlan range is conflicting with other provider network vlan'
     ERR_SINGLE_NIC_VIOLATION = \
-        'Provider and infa networks on the same interface in {}: ' + \
+        'Provider and infra networks on the same interface in {}: ' + \
         'Supported only if all networks on the same interface'
-    ERR_SINGLE_NIC_DPDK = \
-        'Provider and infa networks on the same interface in {}: ' + \
-        'Not supported for %s type of provider networks' % TYPE_OVS_DPDK
+    ERR_SINGLE_NIC_PROVIDER_TYPE = \
+        'Provider and infra networks on the same interface in {0}: ' + \
+        'Not supported for {1} type of provider networks'
     ERR_INFRA_PROVIDER_VLAN_CONFLICT = \
         'Provider network {} vlan range is conflicting with infra network vlan'
     ERR_INFRA_PROVIDER_UNTAGGED_CONFLICT = \
@@ -180,7 +193,12 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
 
     @staticmethod
     def err_provnet_type(iface):
-        err = NetworkProfilesValidation.ERR_INVALID_TYPE.format(iface)
+        err = NetworkProfilesValidation.ERR_INVALID_PROVIDER_TYPE.format(iface)
+        raise validation.ValidationError(err)
+
+    @staticmethod
+    def err_sriov_type(network):
+        err = NetworkProfilesValidation.ERR_INVALID_SRIOV_TYPE.format(network)
         raise validation.ValidationError(err)
 
     @staticmethod
@@ -285,8 +303,8 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
         raise validation.ValidationError(err)
 
     @staticmethod
-    def err_single_nic_dpdk(profile):
-        err = NetworkProfilesValidation.ERR_SINGLE_NIC_DPDK.format(profile)
+    def err_single_nic_provider_type(profile, provider_type):
+        err = NetworkProfilesValidation.ERR_SINGLE_NIC_PROVIDER_TYPE.format(profile, provider_type)
         raise validation.ValidationError(err)
 
     @staticmethod
@@ -582,6 +600,9 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
                 if (self.key_exists(networks[network], self.TRUSTED) and
                         not self.val_is_bool(networks[network], self.TRUSTED)):
                     self.err_not_bool(network, self.TRUSTED)
+                if (self.key_exists(networks[network], self.TYPE) and
+                        networks[network][self.TYPE] not in self.VALID_SRIOV_TYPES):
+                    self.err_sriov_type(network)
                 self.must_be_list(networks, network, self.INTERFACES)
                 for iface in networks[network][self.INTERFACES]:
                     sriov_ifaces.append(iface)
@@ -616,8 +637,8 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
                         if (len(self.conf[profile_name][self.PROVIDER_NETWORK_INTERFACES]) > 1 or
                                 len(self.conf[profile_name][self.INTERFACE_NET_MAPPING]) > 1):
                             self.err_single_nic_violation(profile_name)
-                        if iface_info[self.TYPE] == self.TYPE_OVS_DPDK:
-                            self.err_single_nic_dpdk(profile_name)
+                        if iface_info[self.TYPE] in self.SINGLE_NIC_UNSUPPORTED_TYPES:
+                            self.err_single_nic_provider_type(profile_name, iface_info[self.TYPE])
                         self.validate_shared_infra_provider(network, infra_info, vlan_ranges)
                 for idx, ranges1 in enumerate(vlan_ranges_list):
                     for ranges2 in vlan_ranges_list[(idx+1):]:
@@ -626,10 +647,12 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
 
     def validate_not_part_of_lacp(self, profile_conf, iface):
         if self.key_exists(profile_conf, self.PROVIDER_NETWORK_INTERFACES):
-            for provider_iface in profile_conf[self.PROVIDER_NETWORK_INTERFACES]:
-                if self.is_bond_iface(provider_iface):
-                    if iface in profile_conf[self.BONDING_INTERFACES][provider_iface]:
-                        if profile_conf[self.OVS_BONDING_OPTIONS] == self.MODE_LACP:
+            for prov_iface, prov in profile_conf[self.PROVIDER_NETWORK_INTERFACES].iteritems():
+                if self.is_bond_iface(prov_iface):
+                    if iface in profile_conf[self.BONDING_INTERFACES][prov_iface]:
+                        bonding_type = self.OVS_BONDING_OPTIONS \
+                            if prov[self.TYPE] != self.TYPE_CAAS else self.LINUX_BONDING_OPTIONS
+                        if profile_conf[bonding_type] == self.MODE_LACP:
                             self.err_sriov_lacp_conflict()
                         # part of ovs bonding
                         # do not check linux bonding options even if shared with infra networks
@@ -751,7 +774,7 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
 
     def validate_provider_net_type(self, provnet_ifaces_conf, iface):
         self.must_be_str(provnet_ifaces_conf, iface, self.TYPE)
-        if provnet_ifaces_conf[iface][self.TYPE] not in self.VALID_TYPES:
+        if provnet_ifaces_conf[iface][self.TYPE] not in self.VALID_PROVIDER_TYPES:
             self.err_provnet_type(iface)
 
     def validate_provider_net_vf_count(self, provnet_ifaces_conf, iface):
@@ -785,7 +808,10 @@ class NetworkProfilesValidation(cmvalidator.CMValidator):
         provider_ifaces = []
         if self.key_exists(self.conf[profile_name], self.PROVIDER_NETWORK_INTERFACES):
             for iface in self.conf[profile_name][self.PROVIDER_NETWORK_INTERFACES]:
-                self.validate_net_iface_integrity(profile_name, iface, self.OVS_BONDING_OPTIONS)
+                iface_data = self.conf[profile_name][self.PROVIDER_NETWORK_INTERFACES][iface]
+                bonding_type = self.OVS_BONDING_OPTIONS \
+                    if iface_data[self.TYPE] != self.TYPE_CAAS else self.LINUX_BONDING_OPTIONS
+                self.validate_net_iface_integrity(profile_name, iface, bonding_type)
                 provider_ifaces.append(iface)
         for iface in self.conf[profile_name][self.INTERFACE_NET_MAPPING]:
             if iface not in provider_ifaces:
index 5c92b49..f6e891a 100644 (file)
@@ -22,10 +22,10 @@ from cmdatahandlers.api import validation
 
 class VersionValidation(cmvalidator.CMValidator):
     domain = 'cloud.version'
-    version = [2, 0, 0]
+    version = [2, 0, 2]
 
     # Should be same as 'version' in release build
-    devel_version = [2, 0, 0]
+    devel_version = [2, 0, 2]
 
     # Example:
     # {1: 'This is the first change requiring new template version (1.1.0)',