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