7b9bf3be39027959f17ceeace90800edf451ae12
[vvp/validation-scripts.git] / ice_validator / tests / test_neutron_port_fixed_ips_subnet.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 import re
65
66 import pytest
67
68 from .structures import Heat
69 from .helpers import validates, get_base_template_from_yaml_file
70
71 VERSION = "1.3.0"
72
73 RE_BASE = re.compile(r"(^base$)|(^base_)|(_base_)|(_base$)")  # search pattern
74
75 RE_EXTERNAL_PARAM_SUBNET = re.compile(  # match pattern
76     r"(?P<network_role>.+)(_v6)?_subnet_id$"
77 )
78
79 RE_INTERNAL_PARAM_SUBNET = re.compile(  # match pattern
80     r"int_(?P<network_role>.+)(_v6)?_subnet_id$"
81 )
82
83
84 def get_base(base_template_filepath):
85     """Return the base template's Heat instance.
86     """
87     if base_template_filepath is None:
88         pytest.skip("No base template found")
89     base_template = Heat(filepath=base_template_filepath)
90     return base_template
91
92
93 def run_test(heat_template, validate, validator=None):
94     """call validate for each fixed_ips
95     """
96     heat = Heat(filepath=heat_template)
97     base_template = get_base_template_from_yaml_file(heat_template)
98     if not heat.resources:
99         pytest.skip("No resources found")
100
101     neutron_ports = heat.neutron_port_resources
102     if not neutron_ports:
103         pytest.skip("No OS::Neutron::Port resources found")
104
105     bad = {}
106     for rid, resource in neutron_ports.items():
107         fixed_ips = heat.nested_get(resource, "properties", "fixed_ips")
108         if fixed_ips is None:
109             continue
110         if not isinstance(fixed_ips, list):
111             bad[rid] = "properties.fixed_ips must be a list."
112             continue
113         for fixed_ip in fixed_ips:
114             error = validate(heat, fixed_ip, base_template, validator)
115             if error:
116                 bad[rid] = error
117                 break
118     if bad:
119         # raise RuntimeError(
120         raise AssertionError(
121             "%s"
122             % (",   ".join("%s: %s" % (rid, error) for rid, error in bad.items()))
123         )
124
125
126 def validate_external_fixed_ip_subnet(heat, fixed_ip, base_template, validator):
127     """ensure fixed_ip subnet for external network
128     match the pattern.
129     Returns error message string or None.
130     """
131     subnet = heat.nested_get(fixed_ip, "subnet", "get_param")
132     if subnet:
133         error = validator(subnet, RE_EXTERNAL_PARAM_SUBNET)
134     else:
135         error = None
136     return error
137
138
139 def validate_external_subnet_parameter_format(subnet, regx):
140     """ensure subnet matches template.
141     Returns error message string or None.
142     """
143     if subnet and not subnet.startswith("int_") and regx.match(subnet) is None:
144         return (
145             'fixed_ip subnet parameter "%s" does not match '
146             "{network-role}_subnet_id or {network-role}_v6_subnet_id" % (subnet)
147         )
148     return None
149
150
151 def validate_internal_fixed_ip_subnet(heat, fixed_ip, base_template, validator):
152     """ensure fixed_ip subnet for internal network
153     match the pattern.
154     Returns error message string or None.
155     """
156     base_module = get_base(base_template)
157     subnet = heat.nested_get(fixed_ip, "subnet", "get_param")
158     if subnet:
159         error = validator(heat, base_module, subnet, RE_INTERNAL_PARAM_SUBNET)
160     else:
161         error = None
162     return error
163
164
165 def validate_internal_subnet_parameter_format(heat, base_module, subnet, regx):
166     """ensure if subnet matches template then its parameter exists.
167     Returns error message string or None.
168     """
169     if subnet and subnet.startswith("int_") and regx.match(subnet) is None:
170         return (
171             'fixed_ip subnet parameter "%s" does not match '
172             "int_{network-role}_subnet_id or int_{network-role}_v6_subnet_id" % (subnet)
173         )
174     return None
175
176
177 def validate_internal_subnet_exists_in_base_output(heat, base_module, subnet, regx):
178     """ensure if subnet matches template then its parameter exists.
179     Returns error message string or None.
180     """
181     if (
182         subnet
183         and subnet.startswith("int_")
184         and regx.match(subnet)
185         and heat.nested_get(base_module.outputs, subnet) is None
186     ):
187         return 'fixed_ip subnet(_id) parameter "%s" not in base outputs"' % (subnet)
188     return None
189
190
191 def validate_fixed_ip_subnet(heat, fixed_ip, base_template, validator):
192     """ensure fixed_ip has proper parameters
193     Returns error message string or None.
194     """
195     subnet = heat.nested_get(fixed_ip, "subnet", "get_param")
196     if subnet and heat.nested_get(heat.parameters, subnet, "type") != "string":
197         error = 'subnet parameter "%s" must be type "string"' % subnet
198     else:
199         error = None
200     return error
201
202
203 @validates("R-38236")
204 def test_neutron_port_fixed_ips_subnet(yaml_file):
205     """
206     The VNF's Heat Orchestration Template's
207     resource ``OS::Neutron::Port`` property ``fixed_ips``
208     map property ``subnet``/``subnet_id`` parameter
209     **MUST** be declared type ``string``.
210     """
211     run_test(yaml_file, validate_fixed_ip_subnet)
212
213
214 @validates("R-62802", "R-15287")
215 def test_neutron_port_external_fixed_ips_subnet(yaml_file):
216     """
217     When the VNF's Heat Orchestration Template's
218     resource ``OS::Neutron::Port`` is attaching
219     to an external network,
220     and an IPv4 address is being cloud assigned by OpenStack's DHCP Service
221     and the external network IPv4 subnet is to be specified
222     using the property ``fixed_ips``
223     map property ``subnet``/``subnet_id``, the parameter
224     **MUST** follow the naming convention
225
226       * ``{network-role}_subnet_id``
227     and the external network IPv6 subnet is to be specified
228       * ``{network-role}_v6_subnet_id``
229     """
230     run_test(
231         yaml_file,
232         validate_external_fixed_ip_subnet,
233         validate_external_subnet_parameter_format,
234     )
235
236
237 @validates("R-84123", "R-76160")
238 def test_neutron_port_internal_fixed_ips_subnet(yaml_file):
239     """
240     When
241
242       * the VNF's Heat Orchestration Template's
243         resource ``OS::Neutron::Port`` in an Incremental Module is attaching
244         to an internal network
245         that is created in the Base Module, AND
246       * an IPv4 address is being cloud assigned by OpenStack's DHCP Service AND
247       * the internal network IPv4 subnet is to be specified
248         using the property ``fixed_ips`` map property ``subnet``/``subnet_id``,
249
250     the parameter **MUST** follow the naming convention
251
252       * ``int_{network-role}_subnet_id``
253     an IPv6 address is being cloud assigned by OpenStack's DHCP Service AND
254       * ``int_{network-role}_v6_subnet_id``
255
256     """
257     run_test(
258         yaml_file,
259         validate_internal_fixed_ip_subnet,
260         validate_internal_subnet_parameter_format,
261     )
262
263
264 @validates("R-84123", "R-76160")
265 def test_neutron_port_internal_fixed_ips_subnet_in_base(heat_template):
266     """
267     Only check parent incremental modules, because nested file parameter
268     name may have been changed.
269
270     When
271
272       * the VNF's Heat Orchestration Template's
273         resource ``OS::Neutron::Port`` in an Incremental Module is attaching
274         to an internal network
275         that is created in the Base Module, AND
276       * an IPv4 address is being cloud assigned by OpenStack's DHCP Service AND
277       * the internal network IPv4 subnet is to be specified
278         using the property ``fixed_ips`` map property ``subnet``/``subnet_id``,
279
280     the parameter **MUST** follow the naming convention
281
282       * ``int_{network-role}_subnet_id``
283     an IPv6 address is being cloud assigned by OpenStack's DHCP Service AND
284       * ``int_{network-role}_v6_subnet_id``
285
286     Note that the parameter MUST be defined as an output parameter in
287     the base module.
288     """
289     run_test(
290         heat_template,
291         validate_internal_fixed_ip_subnet,
292         validate_internal_subnet_exists_in_base_output,
293     )