[VVP] updating validation scripts in dublin
[vvp/validation-scripts.git] / ice_validator / tests / test_neutron_port_internal_network.py
1 # -*- coding: utf8 -*-
2 # ============LICENSE_START====================================================
3 # org.onap.vvp/validation-scripts
4 # ===================================================================
5 # Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6 # ===================================================================
7 #
8 # Unless otherwise specified, all software contained herein is licensed
9 # under the Apache License, Version 2.0 (the "License");
10 # you may not use this software except in compliance with the License.
11 # You may obtain a copy of the License at
12 #
13 #             http://www.apache.org/licenses/LICENSE-2.0
14 #
15 # Unless required by applicable law or agreed to in writing, software
16 # distributed under the License is distributed on an "AS IS" BASIS,
17 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 # See the License for the specific language governing permissions and
19 # limitations under the License.
20 #
21 #
22 #
23 # Unless otherwise specified, all documentation contained herein is licensed
24 # under the Creative Commons License, Attribution 4.0 Intl. (the "License");
25 # you may not use this documentation except in compliance with the License.
26 # You may obtain a copy of the License at
27 #
28 #             https://creativecommons.org/licenses/by/4.0/
29 #
30 # Unless required by applicable law or agreed to in writing, documentation
31 # distributed under the License is distributed on an "AS IS" BASIS,
32 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 # See the License for the specific language governing permissions and
34 # limitations under the License.
35 #
36 # ============LICENSE_END============================================
37 #
38 # ECOMP is a trademark and service mark of AT&T Intellectual Property.
39 #
40
41 """
42 resources:
43 {vm-type}_{vm-type_index}_{network-role}_port_{port-index}:
44   type: OS::Neutron::Port
45   properties:
46     network: { get_param: ...}
47     fixed_ips: [ { "ipaddress": { get_param: ... } } ]
48     binding:vnic_type: direct           #only SR-IOV ports, not OVS ports
49     value_specs: {
50       vlan_filter: { get_param: ... },  #all NC ports
51       public_vlans: { get_param: ... }, #all NC ports
52       private_vlans: { get_param: ... },#all NC ports
53       guest_vlans: { get_param: ... },  #SR-IOV Trunk Port only
54       vlan_mirror: { get_param: ... },  #SRIOV Trunk Port
55                                         # Receiving Mirrored Traffic only
56      ATT_FABRIC_CONFIGURATION_REQUIRED: true #all NC ports
57     }
58   metadata:
59     port_type: SR-IOV_Trunk             #SR-IOV Trunk Port
60     port_type: SR-IOV_Non_Trunk         #SR-IOV Non Trunk Port
61     port_type: OVS                      #OVS Port
62     port_type: SR-IOV_Mirrored_Trunk    #SR-IOV Trunk Port
63                                         # Receiving Mirrored Traffic
64 """
65
66 import os.path
67 import re
68
69 import pytest
70
71 from .structures import Heat
72 from .helpers import validates
73
74 VERSION = "1.1.0"
75
76 RE_BASE = re.compile(r"(^base$)|(^base_)|(_base_)|(_base$)")  # search pattern
77 RE_NEUTRON_PORT_RID = re.compile(  # match pattern
78     r"(?P<vm_type>.+)"
79     r"_(?P<vm_type_index>\d+)"
80     r"_(?P<network_role>.+)"
81     r"_port_"
82     r"(?P<port_index>\d+)"
83     r"$"
84 )
85 RE_INTERNAL_NETWORK_PARAM = re.compile(  # match pattern
86     r"int_(?P<network_role>.+)_net_(?P<value_type>id|name)$"
87 )
88 RE_INTERNAL_NETWORK_RID = re.compile(  # match pattern
89     r"int_(?P<network_role>.+)_network$"
90 )
91
92
93 def get_base_template_filepath(yaml_files):
94     """Return first filepath to match RE_BASE
95     """
96     for filepath in yaml_files:
97         basename, __ = os.path.splitext(os.path.basename(filepath))
98         if RE_BASE.search(basename) and basename.find("volume") == -1:
99             return filepath
100     return None
101
102
103 def get_internal_network(yaml_files):
104     """Return the base template's Heat istance.
105     """
106     base_template_filepath = get_base_template_filepath(yaml_files)
107     if base_template_filepath is None:
108         pytest.skip("No base template found")
109     base_template = Heat(filepath=base_template_filepath)
110     for r in base_template.resources.values():
111         # if base_template.nested_get(r, 'type') == 'OS::Neutron::Net':
112         return base_template
113
114     return None
115
116
117 def get_neutron_ports(heat):
118     """Return dict of resource_id: resource, whose type is
119     OS::Neutron::Port.
120     """
121     return {
122         rid: resource
123         for rid, resource in heat.resources.items()
124         if heat.nested_get(resource, "type") == "OS::Neutron::Port"
125     }
126
127
128 # pylint: disable=invalid-name
129
130
131 @validates("R-86182", "R-22688")
132 def test_neutron_port_internal_network(yaml_files):
133     """
134     When the VNF's Heat Orchestration Template's Resource
135     ``OS::Neutron::Port`` is attaching to an internal network (per the
136     ECOMP definition, see Requirements R-52425 and R-46461),
137     and the internal network is created in a
138     different Heat Orchestration Template than the ``OS::Neutron::Port``,
139     the ``network`` parameter name **MUST**
140
141       * follow the naming convention ``int_{network-role}_net_id``
142         if the Neutron
143         network UUID value is used to reference the network
144       * follow the naming convention ``int_{network-role}_net_name`` if the
145         OpenStack network name in is used to reference the network.
146
147     where ``{network-role}`` is the network-role of the internal network and
148     a ``get_param`` **MUST** be used as the intrinsic function.
149
150     In Requirement R-86182, the internal network is created in the VNF's
151     Base Module (Heat Orchestration Template) and the parameter name is
152     declared in the Base Module's ``outputs`` section.
153     When the parameter's value uses a "get_param" function, its name
154     must end in "_name", and when it uses a "get_resource" function,
155     its name must end in "_id".
156
157     The output parameter name will be declared as a parameter in the
158     ``parameters`` section of the incremental module.
159     """
160     internal_network = get_internal_network(yaml_files)
161     if not internal_network:
162         pytest.skip("internal_network template not found")
163
164     if not internal_network.outputs:
165         pytest.skip('internal_network template has no "outputs"')
166
167     for filepath in yaml_files:
168         if filepath != internal_network.filepath:
169             validate_neutron_port(filepath, internal_network)
170
171
172 def validate_neutron_port(filepath, internal_network):
173     """validate the neutron port
174     """
175     heat = Heat(filepath=filepath)
176     if not heat.resources:
177         return
178     neutron_ports = get_neutron_ports(heat)
179     if not neutron_ports:
180         return
181     bad = {}
182     for rid, resource in neutron_ports.items():
183         if not heat.parameters:
184             bad[rid] = 'missing "parameters"'
185             continue
186         network = heat.nested_get(resource, "properties", "network", "get_param")
187         if network is None:
188             bad[rid] = 'missing "network.get_param"'
189             continue
190         if not network.startswith("int_"):
191             continue  # not an internal network port
192         error = validate_param(heat, network, internal_network)
193         if error:
194             bad[rid] = error
195     if bad:
196         raise RuntimeError(
197             "Bad OS::Neutron::Port: %s"
198             % (", ".join("%s: %s" % (rid, error) for rid, error in bad.items()))
199         )
200
201
202 def validate_param(heat, network, internal_network):
203     """Ensure network (the parameter name) is defined in the base
204     template, and has the correct value function.  Ensure its
205     network-role is found in the base template in some
206     OS::Neutron::Net resource.
207     Return error message string, or None if no no errors.
208     """
209     match = RE_INTERNAL_NETWORK_PARAM.match(network)
210     if not match:
211         return 'network.get_param "%s" does not match "%s"' % (
212             network,
213             RE_INTERNAL_NETWORK_PARAM.pattern,
214         )
215     if heat.nested_get(heat.parameters, network) is None:
216         return "missing parameters.%s" % network
217     output = heat.nested_get(internal_network.outputs, network)
218     if not output:
219         return 'network.get_param "%s"' " not found in base template outputs" % network
220     param_dict = match.groupdict()
221     expect = {"name": "get_param", "id": "get_resource"}[param_dict["value_type"]]
222     value = heat.nested_get(output, "value")
223     if heat.nested_get(value, expect) is None:
224         return (
225             'network.get_param "%s" implies its base template'
226             ' output value function should be "%s" dict not "%s"'
227             % (network, expect, value)
228         )
229     network_role = param_dict["network_role"]
230     for rid, resource in internal_network.resources.items():
231         if (
232             heat.nested_get(resource, "type") == "OS::Neutron::Net"
233             or heat.nested_get(resource, "type") == "OS::ContrailV2::VirtualNetwork"
234         ):
235             match = RE_INTERNAL_NETWORK_RID.match(rid)
236             if match and match.groupdict()["network_role"] == network_role:
237                 return None
238     return (
239         "OS::Neutron::Net with network-role"
240         ' "%s" not found in base template."' % network_role
241     )