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,
290 and an IPv4 address is being cloud assigned by OpenStack's DHCP Service
291 and the external network IPv4 subnet is to be specified
292 using the property ``fixed_ips``
293 map property ``subnet``/``subnet_id``, the parameter
294 **MUST** follow the naming convention
296 * ``{network-role}_subnet_id``
297 and the external network IPv6 subnet is to be specified
298 * ``{network-role}_v6_subnet_id``
300 run_test(heat_template, validate_external_fixed_ip)
303 @validates("R-84123", "R-76160")
304 def test_neutron_port_internal_fixed_ips(heat_template):
308 * the VNF's Heat Orchestration Template's
309 resource ``OS::Neutron::Port`` in an Incremental Module is attaching
310 to an internal network
311 that is created in the Base Module, AND
312 * an IPv4 address is being cloud assigned by OpenStack's DHCP Service AND
313 * the internal network IPv4 subnet is to be specified
314 using the property ``fixed_ips`` map property ``subnet``/``subnet_id``,
316 the parameter **MUST** follow the naming convention
318 * ``int_{network-role}_subnet_id``
319 an IPv6 address is being cloud assigned by OpenStack's DHCP Service AND
320 * ``int_{network-role}_v6_subnet_id``
323 run_test(heat_template, validate_internal_fixed_ip)