2 # ============LICENSE_START====================================================
3 # org.onap.vvp/validation-scripts
4 # ===================================================================
5 # Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6 # ===================================================================
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
13 # http://www.apache.org/licenses/LICENSE-2.0
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.
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
28 # https://creativecommons.org/licenses/by/4.0/
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.
36 # ============LICENSE_END============================================
38 # ECOMP is a trademark and service mark of AT&T Intellectual Property.
43 {vm-type}_{vm-type_index}_{network-role}_port_{port-index}:
44 type: OS::Neutron::Port
46 network: { get_param: ...}
47 fixed_ips: [ { "ipaddress": { get_param: ... } } ]
48 binding:vnic_type: direct #only SR-IOV ports, not OVS ports
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
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
72 from .structures import Heat
73 from .helpers import validates
77 RE_BASE = re.compile(r"(^base$)|(^base_)|(_base_)|(_base$)") # search pattern
79 RE_EXTERNAL_PARAM_SUBNET_ID = re.compile( # match pattern
80 r"(?P<network_role>.+)(_v6)?_subnet_id$"
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$')
86 RE_INTERNAL_PARAM_SUBNET_ID = re.compile( # match pattern
87 r"int_(?P<network_role>.+)(_v6)?_subnet_id$"
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$')
94 def get_network(base_template_filepath):
95 """Return the base template's Heat instance.
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():
102 base_template.nested_get(r, "type") == "OS::Neutron::Net"
103 or base_template.nested_get(r, "type") == "OS::ContrailV2::VirtualNetwork"
109 def run_test(heat_template, validate):
110 """call validate for each fixed_ips
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")
117 neutron_ports = heat.neutron_port_resources
118 if not neutron_ports:
119 pytest.skip("No OS::Neutron::Port resources found")
122 for rid, resource in neutron_ports.items():
123 fixed_ips = heat.nested_get(resource, "properties", "fixed_ips")
124 if fixed_ips is None:
126 if not isinstance(fixed_ips, list):
127 bad[rid] = "properties.fixed_ips must be a list."
129 if not heat.parameters:
130 bad[rid] = "fixed_ips requires parameters"
132 for fixed_ip in fixed_ips:
133 error = validate(heat, fixed_ip, base_template)
138 # raise RuntimeError(
139 raise AssertionError(
140 "Bad OS::Neutron::Port: %s"
141 % (", ".join("%s: %s" % (rid, error) for rid, error in bad.items()))
145 def validate_external_fixed_ip(heat, fixed_ip, base_template):
146 """ensure fixed_ip subnet and subnet_id for external network
148 Returns error message string or None.
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)
155 error = validate_external_subnet(subnet)
157 error = validate_external_subnet_id(subnet_id)
163 def validate_external_subnet(subnet):
164 """ensure subnet matches template.
165 Returns error message string or None.
169 and not subnet.startswith("int_")
170 and RE_EXTERNAL_PARAM_SUBNET.match(subnet) is None
172 return 'fixed_ip subnet parameter "%s" does not match "%s"' % (
174 RE_EXTERNAL_PARAM_SUBNET.pattern,
179 def validate_external_subnet_id(subnet_id):
180 """ensure subnet_id matches template.
181 Returns error message string or None.
185 and not subnet_id.startswith("int_")
186 and RE_EXTERNAL_PARAM_SUBNET_ID.match(subnet_id) is None
188 return 'fixed_ip subnet_id parameter "%s" does not match "%s"' % (
190 RE_EXTERNAL_PARAM_SUBNET_ID.pattern,
195 def validate_internal_fixed_ip(heat, fixed_ip, base_template):
196 """ensure fixed_ip subnet and subnet_id for internal network
198 Returns error message string or None.
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)
206 error = validate_internal_subnet(heat, base_module, subnet)
208 error = validate_internal_subnet_id(heat, base_module, subnet_id)
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.
220 and subnet.startswith("int_")
221 and RE_INTERNAL_PARAM_SUBNET.match(subnet)
222 and heat.nested_get(base_module.outputs, subnet) is None
224 return 'fixed_ip subnet parameter "%s" not in base outputs"' % (subnet)
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.
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
238 return 'fixed_ip subnet_id parameter "%s" not in base outputs"' % (subnet_id)
242 def validate_fixed_ip(heat, fixed_ip, base_template):
243 """ensure fixed_ip has proper parameters
244 Returns error message string or None.
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
259 def get_base_template(heat_template):
260 (dirname, filename) = os.path.split(heat_template)
261 files = os.listdir(dirname)
263 basename, __ = os.path.splitext(os.path.basename(file))
266 and basename.find("base") != -1
267 and basename.find("volume") == -1
269 return os.path.join(dirname, "{}{}".format(basename, __))
273 @validates("R-38236")
274 def test_neutron_port_fixed_ips(heat_template):
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``.
281 run_test(heat_template, validate_fixed_ip)
284 @validates("R-62802", "R-15287")
285 def test_neutron_port_external_fixed_ips(heat_template):
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
297 * ``{network-role}_subnet_id``
298 and the external network IPv6 subnet is to be specified
299 * ``{network-role}_v6_subnet_id``
301 run_test(heat_template, validate_external_fixed_ip)
304 @validates("R-84123", "R-76160")
305 def test_neutron_port_internal_fixed_ips(heat_template):
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``,
318 the parameter **MUST** follow the naming convention
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``
325 run_test(heat_template, validate_internal_fixed_ip)