Merge "[VVP] remove tests for 88540, 20947, 54458"
[vvp/validation-scripts.git] / ice_validator / tests / test_neutron_port_fixed_ips.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 #
39
40 """
41 resources:
42 {vm-type}_{vm-type_index}_{network-role}_port_{port-index}:
43   type: OS::Neutron::Port
44   properties:
45     network: { get_param: ...}
46     fixed_ips: [ { "ipaddress": { get_param: ... } } ]
47     binding:vnic_type: direct           #only SR-IOV ports, not OVS ports
48     value_specs: {
49       vlan_filter: { get_param: ... },  #all NC ports
50       public_vlans: { get_param: ... }, #all NC ports
51       private_vlans: { get_param: ... },#all NC ports
52       guest_vlans: { get_param: ... },  #SR-IOV Trunk Port only
53       vlan_mirror: { get_param: ... },  #SRIOV Trunk Port
54                                         # Receiving Mirrored Traffic only
55      ATT_FABRIC_CONFIGURATION_REQUIRED: true #all NC ports
56     }
57   metadata:
58     port_type: SR-IOV_Trunk             #SR-IOV Trunk Port
59     port_type: SR-IOV_Non_Trunk         #SR-IOV Non Trunk Port
60     port_type: OVS                      #OVS Port
61     port_type: SR-IOV_Mirrored_Trunk    #SR-IOV Trunk Port
62                                         # Receiving Mirrored Traffic
63 """
64
65 import os
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.3.0"
75
76 RE_BASE = re.compile(r"(^base$)|(^base_)|(_base_)|(_base$)")  # search pattern
77
78 RE_EXTERNAL_PARAM_SUBNET_ID = re.compile(  # match pattern
79     r"(?P<network_role>.+)(_v6)?_subnet_id$"
80 )
81 RE_EXTERNAL_PARAM_SUBNET = RE_EXTERNAL_PARAM_SUBNET_ID
82 # RE_EXTERNAL_PARAM_SUBNET = re.compile( # match pattern
83 #        r'(?P<network_role>.+)(_v6)?_subnet$')
84
85 RE_INTERNAL_PARAM_SUBNET_ID = re.compile(  # match pattern
86     r"int_(?P<network_role>.+)(_v6)?_subnet_id$"
87 )
88 RE_INTERNAL_PARAM_SUBNET = RE_INTERNAL_PARAM_SUBNET_ID
89 # RE_INTERNAL_PARAM_SUBNET = re.compile( # match pattern
90 #        r'int_(?P<network_role>.+)(_v6)?_subnet$')
91
92
93 def get_network(base_template_filepath):
94     """Return the base template's Heat instance.
95     """
96     if base_template_filepath is None:
97         pytest.skip("No base template found")
98     base_template = Heat(filepath=base_template_filepath)
99     for r in base_template.resources.values():
100         if (
101             base_template.nested_get(r, "type") == "OS::Neutron::Net"
102             or base_template.nested_get(r, "type") == "OS::ContrailV2::VirtualNetwork"
103         ):
104             return base_template
105     return None
106
107
108 def run_test(heat_template, validate):
109     """call validate for each fixed_ips
110     """
111     heat = Heat(filepath=heat_template)
112     base_template = get_base_template(heat_template)
113     if not heat.resources:
114         pytest.skip("No resources found")
115
116     neutron_ports = heat.neutron_port_resources
117     if not neutron_ports:
118         pytest.skip("No OS::Neutron::Port resources found")
119
120     bad = {}
121     for rid, resource in neutron_ports.items():
122         fixed_ips = heat.nested_get(resource, "properties", "fixed_ips")
123         if fixed_ips is None:
124             continue
125         if not isinstance(fixed_ips, list):
126             bad[rid] = "properties.fixed_ips must be a list."
127             continue
128         if not heat.parameters:
129             bad[rid] = "fixed_ips requires parameters"
130             continue
131         for fixed_ip in fixed_ips:
132             error = validate(heat, fixed_ip, base_template)
133             if error:
134                 bad[rid] = error
135                 break
136     if bad:
137         # raise RuntimeError(
138         raise AssertionError(
139             "Bad OS::Neutron::Port: %s"
140             % (", ".join("%s: %s" % (rid, error) for rid, error in bad.items()))
141         )
142
143
144 def validate_external_fixed_ip(heat, fixed_ip, base_template):
145     """ensure fixed_ip subnet and subnet_id for external network
146     match the pattern.
147     Returns error message string or None.
148     """
149     subnet = heat.nested_get(fixed_ip, "subnet", "get_param")
150     subnet_id = heat.nested_get(fixed_ip, "subnet_id", "get_param")
151     if subnet and subnet_id:
152         error = 'fixed_ip %s has both "subnet" and "subnet_id"' % (fixed_ip)
153     elif subnet:
154         error = validate_external_subnet(subnet)
155     elif subnet_id:
156         error = validate_external_subnet_id(subnet_id)
157     else:
158         error = None
159     return error
160
161
162 def validate_external_subnet(subnet):
163     """ensure subnet matches template.
164     Returns error message string or None.
165     """
166     if (
167         subnet
168         and not subnet.startswith("int_")
169         and RE_EXTERNAL_PARAM_SUBNET.match(subnet) is None
170     ):
171         return 'fixed_ip subnet parameter "%s" does not match "%s"' % (
172             subnet,
173             RE_EXTERNAL_PARAM_SUBNET.pattern,
174         )
175     return None
176
177
178 def validate_external_subnet_id(subnet_id):
179     """ensure subnet_id matches template.
180     Returns error message string or None.
181     """
182     if (
183         subnet_id
184         and not subnet_id.startswith("int_")
185         and RE_EXTERNAL_PARAM_SUBNET_ID.match(subnet_id) is None
186     ):
187         return 'fixed_ip subnet_id parameter "%s" does not match "%s"' % (
188             subnet_id,
189             RE_EXTERNAL_PARAM_SUBNET_ID.pattern,
190         )
191     return None
192
193
194 def validate_internal_fixed_ip(heat, fixed_ip, base_template):
195     """ensure fixed_ip subnet and subnet_id for internal network
196     match the pattern.
197     Returns error message string or None.
198     """
199     base_module = get_network(base_template)
200     subnet = heat.nested_get(fixed_ip, "subnet", "get_param")
201     subnet_id = heat.nested_get(fixed_ip, "subnet_id", "get_param")
202     if subnet and subnet_id:
203         error = 'fixed_ip %s has both "subnet" and "subnet_id"' % (fixed_ip)
204     elif subnet:
205         error = validate_internal_subnet(heat, base_module, subnet)
206     elif subnet_id:
207         error = validate_internal_subnet_id(heat, base_module, subnet_id)
208     else:
209         error = None
210     return error
211
212
213 def validate_internal_subnet(heat, base_module, subnet):
214     """ensure if subnet matches template then its parameter exists.
215     Returns error message string or None.
216     """
217     if (
218         subnet
219         and subnet.startswith("int_")
220         and RE_INTERNAL_PARAM_SUBNET.match(subnet)
221         and heat.nested_get(base_module.outputs, subnet) is None
222     ):
223         return 'fixed_ip subnet parameter "%s" not in base outputs"' % (subnet)
224     return None
225
226
227 def validate_internal_subnet_id(heat, base_module, subnet_id):
228     """ensure if subnet_id matches template then its parameter exists.
229     Returns error message string or None.
230     """
231     if (
232         subnet_id
233         and subnet_id.startswith("int_")
234         and RE_INTERNAL_PARAM_SUBNET_ID.match(subnet_id)
235         and heat.nested_get(base_module.outputs, subnet_id) is None
236     ):
237         return 'fixed_ip subnet_id parameter "%s" not in base outputs"' % (subnet_id)
238     return None
239
240
241 def validate_fixed_ip(heat, fixed_ip, base_template):
242     """ensure fixed_ip has proper parameters
243     Returns error message string or None.
244     """
245     subnet = heat.nested_get(fixed_ip, "subnet", "get_param")
246     subnet_id = heat.nested_get(fixed_ip, "subnet_id", "get_param")
247     if subnet and subnet_id:
248         error = 'fixed_ip %s has both "subnet" and "subnet_id"' % (fixed_ip)
249     elif subnet and heat.nested_get(heat.parameters, subnet, "type") != "string":
250         error = 'subnet parameter "%s" must be type "string"' % subnet
251     elif subnet_id and heat.nested_get(heat.parameters, subnet_id, "type") != "string":
252         error = 'subnet_id parameter "%s" must be type "string"' % subnet_id
253     else:
254         error = None
255     return error
256
257
258 def get_base_template(heat_template):
259     (dirname, filename) = os.path.split(heat_template)
260     files = os.listdir(dirname)
261     for file in files:
262         basename, __ = os.path.splitext(os.path.basename(file))
263         if (
264             __ == ".yaml"
265             and basename.find("base") != -1
266             and basename.find("volume") == -1
267         ):
268             return os.path.join(dirname, "{}{}".format(basename, __))
269     return None
270
271
272 @validates("R-38236")
273 def test_neutron_port_fixed_ips(yaml_file):
274     """
275     The VNF's Heat Orchestration Template's
276     resource ``OS::Neutron::Port`` property ``fixed_ips``
277     map property ``subnet``/``subnet_id`` parameter
278     **MUST** be declared type ``string``.
279     """
280     run_test(yaml_file, validate_fixed_ip)
281
282
283 @validates("R-62802", "R-15287")
284 def test_neutron_port_external_fixed_ips(yaml_file):
285     """
286     When the VNF's Heat Orchestration Template's
287     resource ``OS::Neutron::Port`` is attaching
288     to an external network,
289     and an IPv4 address is being cloud assigned by OpenStack's DHCP Service
290     and the external network IPv4 subnet is to be specified
291     using the property ``fixed_ips``
292     map property ``subnet``/``subnet_id``, the parameter
293     **MUST** follow the naming convention
294
295       * ``{network-role}_subnet_id``
296     and the external network IPv6 subnet is to be specified
297       * ``{network-role}_v6_subnet_id``
298     """
299     run_test(yaml_file, validate_external_fixed_ip)
300
301
302 @validates("R-84123", "R-76160")
303 def test_neutron_port_internal_fixed_ips(yaml_file):
304     """
305     When
306
307       * the VNF's Heat Orchestration Template's
308         resource ``OS::Neutron::Port`` in an Incremental Module is attaching
309         to an internal network
310         that is created in the Base Module, AND
311       * an IPv4 address is being cloud assigned by OpenStack's DHCP Service AND
312       * the internal network IPv4 subnet is to be specified
313         using the property ``fixed_ips`` map property ``subnet``/``subnet_id``,
314
315     the parameter **MUST** follow the naming convention
316
317       * ``int_{network-role}_subnet_id``
318     an IPv6 address is being cloud assigned by OpenStack's DHCP Service AND
319       * ``int_{network-role}_v6_subnet_id``
320
321     """
322     run_test(yaml_file, validate_internal_fixed_ip)