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============================================
42 {vm-type}_{vm-type_index}_{network-role}_port_{port-index}:
43 type: OS::Neutron::Port
45 network: { get_param: ...}
46 fixed_ips: [ { "ipaddress": { get_param: ... } } ]
47 binding:vnic_type: direct #only SR-IOV ports, not OVS ports
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
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
71 from .structures import Heat
72 from .helpers import validates
76 RE_BASE = re.compile(r"(^base$)|(^base_)|(_base_)|(_base$)") # search pattern
78 RE_EXTERNAL_PARAM_SUBNET_ID = re.compile( # match pattern
79 r"(?P<network_role>.+)(_v6)?_subnet_id$"
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$')
85 RE_INTERNAL_PARAM_SUBNET_ID = re.compile( # match pattern
86 r"int_(?P<network_role>.+)(_v6)?_subnet_id$"
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$')
93 def get_network(base_template_filepath):
94 """Return the base template's Heat instance.
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():
101 base_template.nested_get(r, "type") == "OS::Neutron::Net"
102 or base_template.nested_get(r, "type") == "OS::ContrailV2::VirtualNetwork"
108 def run_test(heat_template, validate):
109 """call validate for each fixed_ips
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")
116 neutron_ports = heat.neutron_port_resources
117 if not neutron_ports:
118 pytest.skip("No OS::Neutron::Port resources found")
121 for rid, resource in neutron_ports.items():
122 fixed_ips = heat.nested_get(resource, "properties", "fixed_ips")
123 if fixed_ips is None:
125 if not isinstance(fixed_ips, list):
126 bad[rid] = "properties.fixed_ips must be a list."
128 if not heat.parameters:
129 bad[rid] = "fixed_ips requires parameters"
131 for fixed_ip in fixed_ips:
132 error = validate(heat, fixed_ip, base_template)
137 # raise RuntimeError(
138 raise AssertionError(
139 "Bad OS::Neutron::Port: %s"
140 % (", ".join("%s: %s" % (rid, error) for rid, error in bad.items()))
144 def validate_external_fixed_ip(heat, fixed_ip, base_template):
145 """ensure fixed_ip subnet and subnet_id for external network
147 Returns error message string or None.
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)
154 error = validate_external_subnet(subnet)
156 error = validate_external_subnet_id(subnet_id)
162 def validate_external_subnet(subnet):
163 """ensure subnet matches template.
164 Returns error message string or None.
168 and not subnet.startswith("int_")
169 and RE_EXTERNAL_PARAM_SUBNET.match(subnet) is None
171 return 'fixed_ip subnet parameter "%s" does not match "%s"' % (
173 RE_EXTERNAL_PARAM_SUBNET.pattern,
178 def validate_external_subnet_id(subnet_id):
179 """ensure subnet_id matches template.
180 Returns error message string or None.
184 and not subnet_id.startswith("int_")
185 and RE_EXTERNAL_PARAM_SUBNET_ID.match(subnet_id) is None
187 return 'fixed_ip subnet_id parameter "%s" does not match "%s"' % (
189 RE_EXTERNAL_PARAM_SUBNET_ID.pattern,
194 def validate_internal_fixed_ip(heat, fixed_ip, base_template):
195 """ensure fixed_ip subnet and subnet_id for internal network
197 Returns error message string or None.
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)
205 error = validate_internal_subnet(heat, base_module, subnet)
207 error = validate_internal_subnet_id(heat, base_module, subnet_id)
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.
219 and subnet.startswith("int_")
220 and RE_INTERNAL_PARAM_SUBNET.match(subnet)
221 and heat.nested_get(base_module.outputs, subnet) is None
223 return 'fixed_ip subnet parameter "%s" not in base outputs"' % (subnet)
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.
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
237 return 'fixed_ip subnet_id parameter "%s" not in base outputs"' % (subnet_id)
241 def validate_fixed_ip(heat, fixed_ip, base_template):
242 """ensure fixed_ip has proper parameters
243 Returns error message string or None.
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
258 def get_base_template(heat_template):
259 (dirname, filename) = os.path.split(heat_template)
260 files = os.listdir(dirname)
262 basename, __ = os.path.splitext(os.path.basename(file))
265 and basename.find("base") != -1
266 and basename.find("volume") == -1
268 return os.path.join(dirname, "{}{}".format(basename, __))
272 @validates("R-38236")
273 def test_neutron_port_fixed_ips(yaml_file):
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``.
280 run_test(yaml_file, validate_fixed_ip)
283 @validates("R-62802", "R-15287")
284 def test_neutron_port_external_fixed_ips(yaml_file):
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
295 * ``{network-role}_subnet_id``
296 and the external network IPv6 subnet is to be specified
297 * ``{network-role}_v6_subnet_id``
299 run_test(yaml_file, validate_external_fixed_ip)
302 @validates("R-84123", "R-76160")
303 def test_neutron_port_internal_fixed_ips(yaml_file):
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``,
315 the parameter **MUST** follow the naming convention
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``
322 run_test(yaml_file, validate_internal_fixed_ip)