3 # Copyright 2016-2017 RIFT.io Inc
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
29 from scapy
.all
import rdpcap
, UDP
, TCP
, IP
30 gi
.require_version('RwNsrYang', '1.0')
31 from gi
.repository
import (
45 import rift
.auto
.session
46 import rift
.mano
.examples
.ping_pong_nsd
as ping_pong
47 from rift
.auto
.ssh
import SshSession
48 gi
.require_version('RwKeyspec', '1.0')
49 from gi
.repository
.RwKeyspec
import quoted_key
52 @pytest.fixture(scope
='module')
53 def proxy(request
, mgmt_session
):
54 return mgmt_session
.proxy
56 @pytest.fixture(scope
='session')
57 def updated_ping_pong_records(ping_pong_factory
):
58 '''Fixture returns a newly created set of ping and pong descriptors
59 for the create_update tests
61 return ping_pong_factory
.generate_descriptors()
63 @pytest.fixture(scope
='session')
64 def updated_ping_pong_descriptors(updated_ping_pong_records
):
65 '''Fixture which returns a set of updated descriptors that can be configured through
66 the management interface.
68 The descriptors generated by the descriptor generation process for packages don't include project
69 information (presumably in order to avoid tying them to particular project). Here they are converted
70 to types that include project information which can then be used to configure the system.
72 ping
, pong
, ping_pong
= updated_ping_pong_records
73 proj_ping_vnfd
= RwProjectVnfdYang
.YangData_RwProject_Project_VnfdCatalog_Vnfd
.from_dict(ping
.vnfd
.as_dict())
74 proj_pong_vnfd
= RwProjectVnfdYang
.YangData_RwProject_Project_VnfdCatalog_Vnfd
.from_dict(pong
.vnfd
.as_dict())
75 proj_ping_pong_nsd
= RwProjectNsdYang
.YangData_RwProject_Project_NsdCatalog_Nsd
.from_dict(ping_pong
.descriptor
.as_dict()['nsd'][0])
76 return proj_ping_vnfd
, proj_pong_vnfd
, proj_ping_pong_nsd
79 class JobStatusError(Exception):
85 def yield_vnfd_vnfr_pairs(proxy
, nsr
=None):
87 Yields tuples of vnfd & vnfr entries.
90 proxy (callable): Launchpad proxy
91 nsr (optional): If specified, only the vnfr & vnfd records of the NSR
95 Tuple: VNFD and its corresponding VNFR entry
97 def get_vnfd(vnfd_id
):
98 xpath
= "/rw-project:project[rw-project:name='default']/vnfd-catalog/vnfd[id={}]".format(quoted_key(vnfd_id
))
99 return proxy(RwProjectVnfdYang
).get(xpath
)
101 vnfr
= "/rw-project:project[rw-project:name='default']/vnfr-catalog/vnfr"
102 vnfrs
= proxy(RwVnfrYang
).get(vnfr
, list_obj
=True)
103 for vnfr
in vnfrs
.vnfr
:
106 const_vnfr_ids
= [const_vnfr
.vnfr_id
for const_vnfr
in nsr
.constituent_vnfr_ref
]
107 if vnfr
.id not in const_vnfr_ids
:
110 vnfd
= get_vnfd(vnfr
.vnfd
.id)
114 def yield_nsd_nsr_pairs(proxy
):
115 """Yields tuples of NSD & NSR
118 proxy (callable): Launchpad proxy
121 Tuple: NSD and its corresponding NSR record
124 for nsr_cfg
, nsr
in yield_nsrc_nsro_pairs(proxy
):
125 nsd_path
= "/rw-project:project[rw-project:name='default']/nsd-catalog/nsd[id={}]".format(
126 quoted_key(nsr_cfg
.nsd
.id))
127 nsd
= proxy(RwProjectNsdYang
).get_config(nsd_path
)
131 def yield_nsrc_nsro_pairs(proxy
):
132 """Yields tuples of NSR Config & NSR Opdata pairs
135 proxy (callable): Launchpad proxy
138 Tuple: NSR config and its corresponding NSR op record
140 nsr
= "/rw-project:project[rw-project:name='default']/ns-instance-opdata/nsr"
141 nsrs
= proxy(RwNsrYang
).get(nsr
, list_obj
=True)
143 nsr_cfg_path
= "/rw-project:project[rw-project:name='default']/ns-instance-config/nsr[id={}]".format(
144 quoted_key(nsr
.ns_instance_config_ref
))
145 nsr_cfg
= proxy(RwNsrYang
).get_config(nsr_cfg_path
)
150 def assert_records(proxy
):
151 """Verifies if the NSR & VNFR records are created
153 ns_tuple
= list(yield_nsd_nsr_pairs(proxy
))
154 assert len(ns_tuple
) == 1
156 vnf_tuple
= list(yield_vnfd_vnfr_pairs(proxy
))
157 assert len(vnf_tuple
) == 2
160 @pytest.mark
.depends('nsr')
161 @pytest.mark
.setup('records')
162 @pytest.mark
.usefixtures('recover_tasklet')
163 @pytest.mark
.incremental
164 class TestRecordsData(object):
165 def is_valid_ip(self
, address
):
166 """Verifies if it is a valid IP and if its accessible
169 address (str): IP address
175 socket
.inet_pton(socket
.AF_INET
, address
)
179 socket
.inet_pton(socket
.AF_INET6
, address
)
184 def is_ipv6(self
, address
):
185 """Returns True if address is of type 'IPv6', else False."""
187 socket
.inet_pton(socket
.AF_INET6
, address
)
192 @pytest.mark
.feature("recovery")
193 def test_tasklets_recovery(self
, mgmt_session
, proxy
, recover_tasklet
):
194 """Test the recovery feature of tasklets
196 Triggers the vcrash and waits till the system is up
201 rpc_ip
= RwVcsYang
.VCrashInput
.from_dict({"instance_name": comp
})
202 proxy(RwVcsYang
).rpc(rpc_ip
)
204 tasklet_name
= r
'^{}-.*'.format(recover_tasklet
)
206 vcs_info
= proxy(RwBaseYang
).get("/vcs/info/components")
207 for comp
in vcs_info
.component_info
:
208 if comp
.recovery_action
== RECOVERY
and \
209 re
.match(tasklet_name
, comp
.instance_name
):
210 vcrash(comp
.instance_name
)
214 rift
.vcs
.vcs
.wait_until_system_started(mgmt_session
)
215 # NSM tasklet takes a couple of seconds to set up the python structure
216 # so sleep and then continue with the tests.
219 def test_records_present(self
, proxy
):
220 assert_records(proxy
)
222 def test_vnfd_ref_count(self
, proxy
):
225 1. The ref count data of the VNFR with the actual number of VNFRs
227 vnfd_ref_xpath
= "/rw-project:project[rw-project:name='default']/vnfr-catalog/vnfd-ref-count"
228 vnfd_refs
= proxy(RwVnfrYang
).get(vnfd_ref_xpath
, list_obj
=True)
230 expected_ref_count
= collections
.defaultdict(int)
231 for vnfd_ref
in vnfd_refs
.vnfd_ref_count
:
232 expected_ref_count
[vnfd_ref
.vnfd_id_ref
] = vnfd_ref
.instance_ref_count
234 actual_ref_count
= collections
.defaultdict(int)
235 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
236 actual_ref_count
[vnfd
.id] += 1
238 assert expected_ref_count
== actual_ref_count
240 def test_nsr_nsd_records(self
, proxy
):
242 Verifies the correctness of the NSR record using its NSD counter-part
245 1. The count of vnfd and vnfr records
246 2. Count of connection point descriptor and records
248 for nsd
, nsr
in yield_nsd_nsr_pairs(proxy
):
249 assert nsd
.name
== nsr
.nsd_name_ref
250 assert len(nsd
.constituent_vnfd
) == len(nsr
.constituent_vnfr_ref
)
252 assert len(nsd
.vld
) == len(nsr
.vlr
)
253 for vnfd_conn_pts
, vnfr_conn_pts
in zip(nsd
.vld
, nsr
.vlr
):
254 assert len(vnfd_conn_pts
.vnfd_connection_point_ref
) == \
255 len(vnfr_conn_pts
.vnfr_connection_point_ref
)
257 def test_vdu_record_params(self
, proxy
):
260 1. If a valid floating IP has been assigned to the VM
261 2. Count of VDUD and the VDUR
262 3. Check if the VM flavor has been copied over the VDUR
264 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
265 assert vnfd
.mgmt_interface
.port
== vnfr
.mgmt_interface
.port
266 assert len(vnfd
.vdu
) == len(vnfr
.vdur
)
267 for vdud
, vdur
in zip(vnfd
.vdu
, vnfr
.vdur
):
268 for field
in vdud
.vm_flavor
.fields
:
269 if field
in vdur
.vm_flavor
.fields
:
270 assert getattr(vdud
.vm_flavor
, field
) == getattr(vdur
.vm_flavor
, field
)
271 assert self
.is_valid_ip(vdur
.management_ip
) is True
274 for intf
in vdur
.interface
:
275 vdur_intf_dict
[intf
.name
] = intf
.external_connection_point_ref
if 'external_connection_point_ref' in \
276 intf
.as_dict() else intf
.internal_connection_point_ref
277 for intf
in vdud
.interface
:
278 assert intf
.name
in vdur_intf_dict
279 if intf
.internal_connection_point_ref
:
280 vdud_intf_cp_ref
= intf
.internal_connection_point_ref
282 vdud_intf_cp_ref
= intf
.external_connection_point_ref
283 assert vdur_intf_dict
[intf
.name
] == vdud_intf_cp_ref
285 def test_external_vl(self
, proxy
):
288 1. Valid IP for external connection point
289 2. A valid external network fabric
290 3. Connection point names are copied over
291 4. Count of VLD and VLR
292 5. Checks for a valid subnet ?
293 6. Checks for the operational status to be running?
295 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
296 cp_des
, cp_rec
= vnfd
.connection_point
, vnfr
.connection_point
298 assert len(cp_des
) == len(cp_rec
)
299 assert cp_des
[0].name
== cp_rec
[0].name
300 assert self
.is_valid_ip(cp_rec
[0].ip_address
) is True
302 xpath
= "/rw-project:project[rw-project:name='default']/vlr-catalog/vlr[id={}]".format(quoted_key(cp_rec
[0].vlr_ref
))
303 vlr
= proxy(RwVlrYang
).get(xpath
)
305 assert len(vlr
.network_id
) > 0
306 assert len(vlr
.assigned_subnet
) > 0
307 ip
, _
= vlr
.assigned_subnet
.split("/")
308 assert self
.is_valid_ip(ip
) is True
309 assert vlr
.operational_status
== "running"
311 @pytest.mark
.skipif(pytest
.config
.getoption("--port-sequencing"), reason
="port-sequencing test uses two VLs in NSD")
312 def test_nsr_record(self
, proxy
):
314 Currently we only test for the components of NSR tests. Ignoring the
315 operational-events records
318 1. The constituent components.
319 2. Admin status of the corresponding NSD record.
321 for nsr_cfg
, nsr
in yield_nsrc_nsro_pairs(proxy
):
322 # 1 n/w and 2 connection points
323 assert len(nsr
.vlr
) == 2
324 assert len(nsr
.vlr
[0].vnfr_connection_point_ref
) == 2
326 assert len(nsr
.constituent_vnfr_ref
) == 2
327 assert nsr_cfg
.admin_status
== 'ENABLED'
329 def test_wait_for_ns_configured(self
, proxy
):
330 nsr_opdata
= proxy(RwNsrYang
).get('/rw-project:project[rw-project:name="default"]/ns-instance-opdata')
331 nsrs
= nsr_opdata
.nsr
333 assert len(nsrs
) == 1
334 current_nsr
= nsrs
[0]
336 xpath
= "/rw-project:project[rw-project:name='default']/ns-instance-opdata/nsr[ns-instance-config-ref={}]/config-status".format(quoted_key(current_nsr
.ns_instance_config_ref
))
337 proxy(RwNsrYang
).wait_for(xpath
, "configured", timeout
=400)
339 def test_wait_for_pingpong_vnf_configured(self
, proxy
):
340 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
341 xpath
= "/rw-project:project[rw-project:name='default']/vnfr-catalog/vnfr[id={}]/config-status".format(quoted_key(vnfr
.id))
342 proxy(VnfrYang
).wait_for(xpath
, "configured", timeout
=400)
344 def test_vnf_monitoring_params(self
, proxy
):
347 1. The value counter ticks?
348 2. If the meta fields are copied over
350 def mon_param_record(vnfr_id
, mon_param_id
):
351 return '/rw-project:project[rw-project:name="default"]/vnfr-catalog/vnfr[id={}]/monitoring-param[id={}]'.format(
352 quoted_key(vnfr_id
), quoted_key(mon_param_id
))
354 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
355 for mon_des
in (vnfd
.monitoring_param
):
356 mon_rec
= mon_param_record(vnfr
.id, mon_des
.id)
357 mon_rec
= proxy(VnfrYang
).get(mon_rec
)
360 fields
= mon_des
.as_dict().keys()
362 assert getattr(mon_des
, field
) == getattr(mon_rec
, field
)
364 #assert mon_rec.value_integer > 0
366 def test_ns_monitoring_params(self
, logger
, proxy
):
369 1. monitoring-param match in nsd and ns-opdata
370 2. The value counter ticks?
372 mon_param_path
= '/rw-project:project[rw-project:name="default"]/ns-instance-opdata/nsr[ns-instance-config-ref={}]/monitoring-param[id={}]'
373 def fetch_monparam_value(nsr_ref
, mon_param_id
):
374 """Returns the monitoring parameter value"""
375 mon_param
= proxy(NsrYang
).get(mon_param_path
.format(quoted_key(nsr_ref
), quoted_key(mon_param_id
)))
376 return mon_param
.value_integer
378 def check_monparam_value(nsr_ref
, mon_param_id
):
379 """Check if monitoring-param values are getting updated"""
380 recent_mon_param_value
= fetch_monparam_value(nsr_ref
, mon_param_id
)
382 # Monitor the values over a period of 60 secs. Fail the test if there is no update in mon-param value.
384 while (time
.time() - s_time
) < 60:
385 if fetch_monparam_value(nsr_ref
, mon_param_id
) > recent_mon_param_value
:
388 assert False, 'mon-param values are not getting updated. Last value was {}'.format(recent_mon_param_value
)
390 for nsd
, nsr
in yield_nsd_nsr_pairs(proxy
):
391 assert len(nsd
.monitoring_param
) == len(nsr
.monitoring_param
)
392 for mon_param
in nsr
.monitoring_param
:
393 logger
.info('Verifying monitoring-param: {}'.format(mon_param
.as_dict()))
394 check_monparam_value(nsr
.ns_instance_config_ref
, mon_param
.id)
396 def test_cm_nsr(self
, proxy
, use_accounts
):
399 1. The ID of the NSR in cm-state
400 2. Name of the cm-nsr
401 3. The vnfr component's count
402 4. State of the cm-nsr
404 for nsd
, nsr
in yield_nsd_nsr_pairs(proxy
):
405 con_nsr_xpath
= "/rw-project:project[rw-project:name='default']/cm-state/cm-nsr[id={}]".format(
406 quoted_key(nsr
.ns_instance_config_ref
))
407 con_data
= proxy(RwConmanYang
).get(con_nsr_xpath
)
410 assert con_data
.name
== rift
.auto
.mano
.resource_name(nsd
.name
)
412 assert len(con_data
.cm_vnfr
) == 2
414 state_path
= con_nsr_xpath
+ "/state"
415 proxy(RwConmanYang
).wait_for(state_path
, 'ready', timeout
=120)
417 def test_cm_vnfr(self
, proxy
):
420 1. The ID of Vnfr in cm-state
423 4. Checks for a reachable IP in mgmt_interface
424 5. Basic checks for connection point
426 def is_reachable(ip
, timeout
=10):
427 rc
= subprocess
.call(["ping", "-c1", "-w", str(timeout
), ip
])
432 nsr_cfg
, _
= list(yield_nsrc_nsro_pairs(proxy
))[0]
433 con_nsr_xpath
= "/rw-project:project[rw-project:name='default']/cm-state/cm-nsr[id={}]".format(quoted_key(nsr_cfg
.id))
435 for _
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
436 con_vnfr_path
= con_nsr_xpath
+ "/cm-vnfr[id={}]".format(quoted_key(vnfr
.id))
437 con_data
= proxy(RwConmanYang
).get(con_vnfr_path
)
439 assert con_data
is not None
441 state_path
= con_vnfr_path
+ "/state"
442 proxy(RwConmanYang
).wait_for(state_path
, 'ready', timeout
=120)
444 con_data
= proxy(RwConmanYang
).get(con_vnfr_path
)
445 assert is_reachable(con_data
.mgmt_interface
.ip_address
) is True
447 if pytest
.config
.getoption("--port-sequencing"):
448 # there are more than one connection point in the VNFDs for port sequencing test
449 # there is no distinction between icp and cp in 'show cm-state'.
450 # both icp and cp come under connection-point in 'show cm-state'
451 vnfr_intl_extl_connection_points_dict
= {}
452 for icp
in vnfr
.vdur
[0].internal_connection_point
:
453 vnfr_intl_extl_connection_points_dict
[icp
.name
] = icp
.ip_address
454 for cp
in vnfr
.connection_point
:
455 vnfr_intl_extl_connection_points_dict
[cp
.name
] = cp
.ip_address
457 assert len(con_data
.connection_point
) == len(vnfr_intl_extl_connection_points_dict
)
458 for cp
in con_data
.connection_point
:
459 assert cp
.name
in vnfr_intl_extl_connection_points_dict
460 assert cp
.ip_address
== vnfr_intl_extl_connection_points_dict
[cp
.name
]
462 assert len(con_data
.connection_point
) == 2
463 connection_point
= con_data
.connection_point
[0]
464 assert connection_point
.name
== vnfr
.connection_point
[0].name
465 assert connection_point
.ip_address
== vnfr
.connection_point
[0].ip_address
468 not (pytest
.config
.getoption("--static-ip") or pytest
.config
.getoption("--update-vnfd-instantiate")),
469 reason
="need --static-ip or --update-vnfd-instantiate option to run")
470 def test_static_ip(self
, proxy
, logger
, vim_clients
, cloud_account_name
):
473 1. static-ip match in vnfd and vnfr
474 2. static-ip match in cm-state
475 3. Get the IP of openstack VM. Match the static-ip
476 4. Check if the VMs are reachable from each other (Skip if type of static ip addresses is IPv6)
478 nsr_cfg
, _
= list(yield_nsrc_nsro_pairs(proxy
))[0]
479 con_nsr_xpath
= "/rw-project:project[rw-project:name='default']/cm-state/cm-nsr[id={}]".format(quoted_key(nsr_cfg
.id))
482 static_ip_vnfd
= False
483 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
484 if vnfd
.vdu
[0].interface
[1].static_ip_address
:
485 static_ip_vnfd
= True
486 assert vnfd
.vdu
[0].interface
[1].static_ip_address
== vnfr
.connection_point
[1].ip_address
487 if 'ping' in vnfd
.name
:
488 ips
['mgmt_ip'] = vnfr
.vdur
[0].management_ip
490 ips
['static_ip'] = vnfd
.vdu
[0].interface
[1].static_ip_address
492 con_vnfr_path
= con_nsr_xpath
+ "/cm-vnfr[id={}]".format(quoted_key(vnfr
.id))
493 con_data
= proxy(RwConmanYang
).get(con_vnfr_path
)
495 assert con_data
is not None
496 assert con_data
.connection_point
[1].ip_address
== vnfd
.vdu
[0].interface
[1].static_ip_address
498 xpath
= "/rw-project:project[rw-project:name='default']/vlr-catalog/vlr[id={}]".format(quoted_key(vnfr
.connection_point
[1].vlr_ref
))
499 vlr
= proxy(RwVlrYang
).get(xpath
)
501 vim_client
= vim_clients
[cloud_account_name
]
502 vm_property
= vim_client
.nova_server_get(vnfr
.vdur
[0].vim_id
)
503 logger
.info('VM properties for {}: {}'.format(vnfd
.name
, vm_property
))
505 addr_prop_list
= vm_property
['addresses'][vlr
.name
]
506 logger
.info('addresses attribute: {}'.format(addr_prop_list
))
508 addr_prop
= [addr_prop
for addr_prop
in addr_prop_list
if addr_prop
['addr'] == vnfr
.connection_point
[1].ip_address
]
511 assert static_ip_vnfd
# if False, then none of the VNF descriptors' connections points are carrying static-ip-address field.
513 # Check if the VMs are reachable from each other
514 username
, password
= ['fedora'] * 2
515 ssh_session
= SshSession(ips
['mgmt_ip'])
517 assert ssh_session
.connect(username
=username
, password
=password
)
518 if not self
.is_ipv6(ips
['static_ip']):
519 assert ssh_session
.run_command('ping -c 5 {}'.format(ips
['static_ip']))[0] == 0
521 @pytest.mark
.skipif(not pytest
.config
.getoption("--vnf-dependencies"), reason
="need --vnf-dependencies option to run")
522 def test_vnf_dependencies(self
, proxy
):
525 1. Match various config parameter sources with config primitive parameters
526 Three types of sources are being verified for pong vnfd.
527 Attribute: A runtime value like IP address of a connection point (../../../mgmt-interface, ip-address)
528 Descriptor: a XPath to a leaf in the VNF descriptor/config (../../../mgmt-interface/port)
529 Value: A pre-defined constant ('admin' as mentioned in pong descriptor)
530 2. Match the config-parameter-map defined in NS descriptor
531 There used to be a check to verify config parameter values in cm-state (cm-state/cm-nsr/cm-vnfr/config-parameter).
532 Recently that got removed due to confd issue. So, there is no such check currently for cm-state.
534 nsr_cfg
, _
= list(yield_nsrc_nsro_pairs(proxy
))[0]
535 con_nsr_xpath
= "/rw-project:project[rw-project:name='default']/cm-state/cm-nsr[id={}]".format(quoted_key(nsr_cfg
.id))
537 pong_source_map
, ping_request_map
= None, None
539 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
540 # Get cm-state for this vnfr
541 con_vnfr_path
= con_nsr_xpath
+ "/cm-vnfr[id={}]".format(quoted_key(vnfr
.id))
542 con_data
= proxy(RwConmanYang
).get(con_vnfr_path
)
544 # Match various config parameter sources with config primitive parameters
545 for config_primitive
in vnfr
.vnf_configuration
.config_primitive
:
546 if config_primitive
.name
in ("config", "start-stop"):
547 for parameter
in config_primitive
.parameter
:
548 if parameter
.name
== 'mgmt_ip':
549 assert parameter
.default_value
== vnfr
.mgmt_interface
.ip_address
550 if parameter
.name
== 'mgmt_port':
551 assert parameter
.default_value
== str(vnfd
.mgmt_interface
.port
)
552 if parameter
.name
== 'username':
553 assert parameter
.default_value
== 'admin'
555 # Fetch the source parameter values from pong vnf and request parameter values from ping vnf
556 if config_primitive
.name
== "config":
557 if vnfd
.name
== "pong_vnfd":
558 pong_source_map
= [parameter
.default_value
for parameter
in config_primitive
.parameter
if
559 parameter
.name
in ("service_ip", "service_port")]
560 if vnfd
.name
== "ping_vnfd":
561 ping_request_map
= [parameter
.default_value
for parameter
in config_primitive
.parameter
if
562 parameter
.name
in ("pong_ip", "pong_port")]
563 assert pong_source_map
564 assert ping_request_map
565 # Match the config-parameter-map defined in NS descriptor
566 assert sorted(pong_source_map
) == sorted(ping_request_map
)
568 @pytest.mark
.skipif(not pytest
.config
.getoption("--port-security"), reason
="need --port-security option to run")
569 def test_port_security(self
, proxy
, vim_clients
, cloud_account_name
):
572 1. port-security-enabled match in vnfd and vnfr
573 2. Get port property from openstack. Match these attributes: 'port_security_enabled', 'security_groups'
575 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
576 assert vnfd
.connection_point
[1].port_security_enabled
== vnfr
.connection_point
[1].port_security_enabled
578 xpath
= "/rw-project:project[rw-project:name='default']/vlr-catalog/vlr[id={}]".format(quoted_key(vnfr
.connection_point
[1].vlr_ref
))
579 vlr
= proxy(RwVlrYang
).get(xpath
)
581 vim_client
= vim_clients
[cloud_account_name
]
582 port
= [port
for port
in vim_client
.neutron_port_list() if port
['network_id'] == vlr
.network_id
if
583 port
['name'] == vnfr
.connection_point
[1].name
]
586 port_openstack
= port
[0]
587 assert vnfr
.connection_point
[1].port_security_enabled
== port_openstack
['port_security_enabled']
589 if vnfr
.connection_point
[1].port_security_enabled
:
590 assert port_openstack
['security_groups'] # It has to carry at least one security group if enabled
592 assert not port_openstack
['security_groups']
594 @pytest.mark
.skipif(not pytest
.config
.getoption("--port-sequencing"), reason
="need --port-sequencing option to run")
595 def test_explicit_port_sequencing(self
, proxy
, vim_clients
, cloud_account_name
, logger
, port_sequencing_intf_positions
, iteration
):
598 1. Interface count match in vnfd and vnfr
599 2. Get interface ordering(mac address) from VM using 'ip a' command; From output of neutron port-list, get
600 corresponding connection point names in the same order as mac address ordered list.
601 3. Get interface ordering from the vnfd/vdu
602 4. Compare lists from step-2 and step-3
604 username
, password
= ['fedora']*2
606 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
607 assert len(vnfd
.vdu
[0].interface
) == len(vnfr
.vdur
[0].interface
)
609 logger
.debug('Interface details for vnfd {}: {}'.format(vnfd
.name
, vnfd
.vdu
[0].as_dict()['interface']))
612 tmp_positional_values_list
= []
613 for intf
in vnfr
.vdur
[0].interface
:
614 # if no position is specified for an interface, then vnfr/vdur/interface carries 0 as its positional value
616 tmp_positional_values_list
.append(intf
.position
)
617 if 'ping' in vnfd
.name
:
618 assert not tmp_positional_values_list
619 if 'pong' in vnfd
.name
:
620 assert set(tmp_positional_values_list
) == set(port_sequencing_intf_positions
)
622 # Get a sorted list of interfaces from vnfd/vdu
623 icp_key_name
, ecp_key_name
= 'internal_connection_point_ref', 'external_connection_point_ref'
624 intf_with_position_field_dict
, intf_without_position_field_list
= {}, []
626 for intf
in vnfd
.vdu
[0].interface
:
627 intf
= intf
.as_dict()
628 cp_ref_key
= icp_key_name
if icp_key_name
in intf
else ecp_key_name
629 if 'position' in intf
:
630 intf_with_position_field_dict
[intf
['position']] = intf
[cp_ref_key
]
632 intf_without_position_field_list
.append(intf
[cp_ref_key
])
634 intf_with_position_field_list
= sorted(intf_with_position_field_dict
.items(), key
=operator
.itemgetter(0))
635 sorted_cp_names_in_vnfd
= [pos_cpname_tuple
[1] for pos_cpname_tuple
in intf_with_position_field_list
] + \
636 sorted(intf_without_position_field_list
)
638 # Establish a ssh session to VDU to get mac address list sorted by interfaces
639 ssh_session
= SshSession(vnfr
.vdur
[0].management_ip
)
641 assert ssh_session
.connect(username
=username
, password
=password
)
642 e_code
, ip_output
, err
= ssh_session
.run_command('sudo ip a')
644 logger
.debug('Output of "ip a": {}'.format(ip_output
))
645 mac_addr_list
= re
.findall(r
'link/ether\s+(.*)\s+brd', ip_output
)
647 # exclude eth0 as it is always a mgmt-interface
648 interface_starting_index
= len(mac_addr_list
) - len(vnfd
.vdu
[0].interface
)
649 mac_addr_list
= mac_addr_list
[interface_starting_index
: ]
651 # Get neutron port list
652 neutron_port_list
= vim_clients
[cloud_account_name
].neutron_port_list()
654 # Get those ports whose mac_address value matches with one of the mac addresses in mac_addr_list
655 # This new list is already sorted as the outer loop iterates over mac_addr_list
656 sorted_cp_names_in_vm
= [neutron_port_dict
['name'] for mac
in mac_addr_list
for neutron_port_dict
in neutron_port_list
657 if mac
==neutron_port_dict
['mac_address']]
659 logger
.debug('Sorted connection points as per "ip a" in VM: {}'.format(sorted_cp_names_in_vm
))
660 logger
.debug('Sorted connection points as per ordering mentioned in vnfd: {}'.format(sorted_cp_names_in_vnfd
))
662 assert sorted_cp_names_in_vm
== sorted_cp_names_in_vnfd
665 not (pytest
.config
.getoption("--vnf-dependencies") and
666 pytest
.config
.getoption("--service-primitive")),
667 reason
="need --vnf-dependencies and --service-primitive option to run")
669 self
, mgmt_session
, cloud_module
, cloud_account
, descriptors
,
670 fmt_nsd_catalog_xpath
, logger
):
671 """Testing service primitives and config primitives."""
672 # Create a cloud account
673 rift
.auto
.mano
.create_cloud_account(
674 mgmt_session
, cloud_account
, "default")
676 rwnsr_pxy
= mgmt_session
.proxy(RwNsrYang
)
677 nsr_pxy
= mgmt_session
.proxy(NsrYang
)
678 rwvnfr_pxy
= mgmt_session
.proxy(RwVnfrYang
)
680 # Testing a custom service primitive
681 ns_opdata
= rwnsr_pxy
.get(
682 '/rw-project:project[rw-project:name="default"]' +
683 '/ns-instance-opdata/nsr'
685 nsr_id
= ns_opdata
.ns_instance_config_ref
686 sp_rpc_input
= NsrYang
.YangInput_Nsr_ExecNsServicePrimitive
.from_dict(
687 {'name': 'primitive_test', 'nsr_id_ref': nsr_id
})
688 nsr_pxy
.rpc(sp_rpc_input
)
690 # Testing a config primitive
691 vnfr_catalog
= rwvnfr_pxy
.get(
692 '/rw-project:project[rw-project:name="default"]' +
695 cp_rpc_input
= NsrYang
.YangInput_Nsr_ExecNsServicePrimitive
.from_dict(
696 {'nsr_id_ref': nsr_id
})
697 vnf_list
= cp_rpc_input
.create_vnf_list()
698 vnf_primitive
= vnf_list
.create_vnf_primitive()
699 vnf_primitive
.index
= 1
700 vnf_primitive
.name
= "start-stop"
701 vnf_list
.member_vnf_index_ref
= (
702 vnfr_catalog
.vnfr
[0].member_vnf_index_ref
704 vnf_list
._set
_vnfr
_id
_ref
(vnfr_catalog
.vnfr
[0].id)
705 vnf_list
.vnf_primitive
.append(vnf_primitive
)
706 cp_rpc_input
.vnf_list
.append(vnf_list
)
707 nsr_pxy
.rpc(cp_rpc_input
)
708 # Checking nsd joblist to see if both tests passed
710 def check_job_status(status
=None):
711 ns_opdata
= rwnsr_pxy
.get(
712 '/rw-project:project[rw-project:name="default"]' +
713 '/ns-instance-opdata/nsr'
717 for idx
in range(0, counter_limit
):
718 if ns_opdata
.config_agent_job
[idx
].job_status
== 'failure':
720 'Service primitive test failed.' +
721 ' The config agent reported failure job status')
722 raise JobStatusError(err_msg
)
724 elif ns_opdata
.config_agent_job
[idx
].job_status
== 'success':
728 if counter
== counter_limit
:
734 start_time
= time
.time()
735 while (time
.time() - start_time
< 60):
736 status
= check_job_status()
741 'Service primitive test failed. Timed out: 60 seconds' +
742 'The config agent never reached a success status')
743 raise JobStatusError(err_msg
)
746 not (pytest
.config
.getoption("--metadata-vdud") or pytest
.config
.getoption("--metadata-vdud-cfgfile")),
747 reason
="need --metadata-vdud or --metadata-vdud-cfgfile option to run")
748 def test_metadata_vdud(self
, logger
, proxy
, vim_clients
, cloud_account_name
, metadata_host
):
751 1. content of supplemental-boot-data match in vnfd and vnfr
752 vnfr may carry extra custom-meta-data fields (e.g pci_assignement) which are by default enabled during VM creation by openstack.
753 vnfr doesn't carry config_file details; so that will be skipped during matching.
754 2. boot-data-drive match with openstack VM's config_drive attribute
755 3. For each VDUD which have config-file fields mentioned, check if there exists a path in the VM which
756 matches with config-file's dest field. (Only applicable for cirros_cfgfile_vnfd VNF RIFT-15524)
757 4. For each VDUD, match its custom-meta-data fields with openstack VM's properties field
759 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
760 if any(name
in vnfd
.name
for name
in ['ping', 'pong', 'fedora']):
761 username
, password
= ['fedora'] * 2
762 elif 'ubuntu' in vnfd
.name
:
763 username
, password
= ['ubuntu'] * 2
764 elif 'cirros' in vnfd
.name
:
765 username
, password
= 'cirros', 'cubswin:)'
767 assert False, 'Not expected to use this VNFD {} in this systemtest. VNFD might have changed. Exiting the test.'.format(
770 # Wait till VNF's operational-status becomes 'running'
771 # The below check is usually covered as part of test_wait_for_ns_configured
772 # But, this is mostly needed when non- ping pong packages are used e.g cirrus cfgfile package
773 xpath
= "/rw-project:project[rw-project:name='default']/vnfr-catalog/vnfr[id={}]/operational-status".format(quoted_key(vnfr
.id))
774 proxy(VnfrYang
).wait_for(xpath
, "running", timeout
=300)
777 # Get the VDU details from openstack
778 vim_client
= vim_clients
[cloud_account_name
]
779 vm_property
= vim_client
.nova_server_get(vnfr
.vdur
[0].vim_id
)
780 logger
.info('VM property for {}: {}'.format(vnfd
.name
, vm_property
))
782 # Establish a ssh session to VDU
783 ssh_session
= SshSession(vnfr
.vdur
[0].management_ip
)
785 assert ssh_session
.connect(username
=username
, password
=password
)
787 assert vnfd
.vdu
[0].supplemental_boot_data
.boot_data_drive
== vnfr
.vdur
[
788 0].supplemental_boot_data
.boot_data_drive
== bool(vm_property
['config_drive'])
789 # Using bool() because vm_property['config_drive'] returns 'True' or '' whereas vnfr/vnfd returns True/False
791 # Assert 3: only for cirros vnf
792 if 'cirros' in vnfd
.name
:
793 for config_file
in vnfd
.vdu
[0].supplemental_boot_data
.config_file
:
794 assert ssh_session
.run_command('test -e {}'.format(config_file
.dest
))[0] == 0
796 vdur_metadata
= {metadata
.name
: metadata
.value
for metadata
in
797 vnfr
.vdur
[0].supplemental_boot_data
.custom_meta_data
}
799 # Get the user-data/metadata from VM
800 e_code
, vm_metadata
, _
= ssh_session
.run_command(
801 'curl http://{}/openstack/latest/meta_data.json'.format(metadata_host
))
803 vm_metadata
= json
.loads(vm_metadata
)['meta']
804 logger
.debug('VM metadata for {}: {}'.format(vnfd
.name
, vm_metadata
))
806 for vdud_metadata
in vnfd
.vdu
[0].supplemental_boot_data
.custom_meta_data
:
807 assert vdud_metadata
.value
== vdur_metadata
[vdud_metadata
.name
]
808 assert vdud_metadata
.value
== vm_metadata
[vdud_metadata
.name
]
810 @pytest.mark
.skipif(not pytest
.config
.getoption("--multidisk"), reason
="need --multidisk option to run")
811 def test_multidisk(self
, logger
, proxy
, vim_clients
, cloud_account_name
, multidisk_testdata
):
813 This feature is only supported in openstack, brocade vCPE.
815 1. volumes match in vnfd and vnfr
816 2. volumes match in vnfr and openstack host
817 Check no of volumes attached to the VNF VM. It should match no of volumes defined in VDUD.
818 Match volume names. In 'openstack volume show <vol_uuid>', the device should be /dev/<volume_name_in_vdud>
819 Match the volume source.
820 Match the volume size.
821 Match the Volume IDs mentioned in VNFR with openstack volume's ID.
823 ping_test_data
, pong_test_data
= multidisk_testdata
824 vol_attr
= ['device_type', None, 'size', 'image', 'boot_priority']
825 # device_bus doesn't appear in vnfr/vdur
827 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
):
828 logger
.info('Verifying VNF {}'.format(vnfd
.name
))
829 vnf_testdata
= ping_test_data
if 'ping' in vnfd
.name
else pong_test_data
831 # Assert 1: Match volumes in vnfd, vnfr, test data
832 assert len(vnfd
.vdu
[0].volumes
) == len(vnfr
.vdur
[0].volumes
)
834 for vnfr_vol
in vnfr
.vdur
[0].volumes
:
835 logger
.info('Verifying vnfr volume: {}'.format(vnfr_vol
.as_dict()))
836 vnfd_vol
= [vol
for vol
in vnfd
.vdu
[0].volumes
if vol
.name
==vnfr_vol
.name
][0]
838 vol_testdata
= vnf_testdata
[vnfr_vol
.name
]
840 for i
, attr
in enumerate(vol_attr
):
841 if attr
== None: # device_bus doesn't appear in vnfr/vdur
843 if i
== 3 and (vol_testdata
[i
]==None or getattr(vnfd_vol
, 'ephemeral')):
844 # volume source of type ephemeral doesn't appear in vnfr/vdur
845 # If no image is defined for a volume, getattr(vnfr_vol, 'ephemeral') returns False. Strange. RIFT-15165
846 assert not getattr(vnfd_vol
, 'image')
849 assert getattr(vnfd_vol
, attr
) == getattr(vnfr_vol
, attr
)
850 if vol_testdata
[i
] is not None:
851 assert getattr(vnfd_vol
, attr
) == vol_testdata
[i
]
853 # Assert 2: Volumes match in vnfr and openstack host
854 # Get VM properties from the VIM
855 vim_client
= vim_clients
[cloud_account_name
]
856 vm_property
= vim_client
.nova_server_get(vnfr
.vdur
[0].vim_id
)
857 logger
.info('VIM- VM properties: {}'.format(vm_property
))
859 # Get the volumes attached to this VNF VM
860 vim_volumes
= vm_property
['os-extended-volumes:volumes_attached']
861 logger
.info('VIM- Volumes attached to this VNF VM: {}'.format(vim_volumes
))
864 assert len(vim_volumes
) == len(vnfr
.vdur
[0].volumes
)
866 vnfr_volumes_by_id
= {vol
.volume_id
:vol
for vol
in vnfr
.vdur
[0].volumes
}
867 for vim_volume
in vim_volumes
:
868 # Match the Volume IDs mentioned in VNFR with openstack volume's ID.
869 logger
.info('Verifying volume: {}'.format(vim_volume
['id']))
870 assert vim_volume
['id'] in vnfr_volumes_by_id
.keys()
871 vnfr_vol_
= vnfr_volumes_by_id
[vim_volume
['id']]
873 # Get volume details. Equivalent cli: openstack volume show <uuid>
874 vim_vol_attrs
= vim_client
.cinder_volume_get(vim_volume
['id'])
877 assert vnfr_vol_
.size
== vim_vol_attrs
.size
879 # Match volume source
880 if vnfr_vol_
.image
: # To make sure this is not ephemeral type
881 logger
.info('VIM- Image details of the volume: {}'.format(vim_vol_attrs
.volume_image_metadata
))
882 assert vnfr_vol_
.image
== vim_vol_attrs
.volume_image_metadata
['image_name']
884 assert not hasattr(vim_vol_attrs
, 'volume_image_metadata')
886 # Match volume name e.g 'device': u'/dev/vdf'
887 logger
.info('Verifying [{}] in attached volumes {}'.format(vnfr_vol_
.name
, vim_vol_attrs
.attachments
))
888 assert [attachment
for attachment
in vim_vol_attrs
.attachments
if vnfr_vol_
.name
in attachment
['device']]
890 @pytest.mark
.skipif(not pytest
.config
.getoption("--l2-port-chaining"), reason
="need --l2-port-chaining option to run")
891 def test_l2_port_chaining(self
, proxy
):
893 It uses existing NS, VNF packages: $RIFT_INSTALL/usr/rift/mano/nsds/vnffg_demo_nsd/vnffg_l2portchain_*.
894 This test function is specific to these packages. Those VNFs use Ubuntu trusty image ubuntu_trusty_1404.qcow2.
896 1. Count of VNFFG in nsd and nsr
897 2. Count of rsp, classifier in VNFFG descriptor and VNFFG record
898 3. Need details what other fields need to be matched in nsd and nsr
899 4. Traffic flows through internal hops as per the classifier and rsp
900 As per the classifiers in NS package, the following flows will be tested.
901 - Tcp packets with dest port 80 starting from pgw VNF should go through Firewall VNF.
902 - Udp packets with source port 80 starting from router VNF should go through nat->dpi
903 - Udp packets with dest port 80 starting from pgw VNF should go through dpi->nat
906 UDP_PROTOCOL
, TCP_PROTOCOL
= 17, 6
908 def pcap_analysis(pcap_file
, src_add
, dst_add
, src_port
=None, dst_port
=None, protocol
=6):
909 """Analyse packets in a pcap file and return True if there is a packet match w.r.t src_addr, dst_addr, protocol.
911 pcap_file: pcap file that is generated by traffic analysis utility such as tcpdump
912 src_add, dst_addr: Source & dest IP which need to be matched for a packet
913 protocol: Protocol that needs to be matched for a packet which already matched src_addr, dst_addr (protocol accepts integer e.g TCP 6, UDP 17)
916 timestamp of the packet which is matched (Needed to check packet flow order through VNFs)
918 False: if there is no packet match
920 It uses scapy module to analyse pcap file. pip3 install scapy-python3
921 Other options https://pypi.python.org/pypi/pypcapfile
923 assert os
.path
.exists(pcap_file
)
924 pkt_type
= TCP
if protocol
==6 else UDP
926 pcap_obj
= rdpcap(pcap_file
)
929 if not(pkt
[IP
].src
==src_add
and pkt
[IP
].dst
==dst_add
and pkt
[IP
].proto
==protocol
):
933 if not (pkt
[pkt_type
].sport
==src_port
):
936 if not (pkt
[pkt_type
].dport
==dst_port
):
941 # Check the VNFFG in nsd and nsr
942 for nsd
, nsr
in yield_nsd_nsr_pairs(proxy
):
945 assert len(vnffgds
) == len(vnffgrs
)
947 # Check the classifier, rsp in nsd and nsr
948 for vnffgd
in vnffgds
:
949 vnffgr
= [vnffgr
for vnffgr
in vnffgrs
if vnffgd
.id == vnffgr
.vnffgd_id_ref
][0]
950 assert len(vnffgd
.rsp
) == len(vnffgr
.rsp
)
951 assert len(vnffgd
.classifier
) == len(vnffgr
.classifier
)
953 vnfrs
= proxy(RwVnfrYang
).get('/rw-project:project[rw-project:name="default"]/vnfr-catalog/vnfr', list_obj
=True)
956 vm_names
= ('router', 'firewall', 'dpi', 'nat', 'pgw')
957 vm_ips
= {vm_name
: vnfr
.vdur
[0].vm_management_ip
for vm_name
in vm_names
for vnfr
in vnfrs
.vnfr
if
958 vm_name
in vnfr
.name
}
959 vm_cp_ips
= {vm_name
: vnfr
.connection_point
[0].ip_address
for vm_name
in vm_names
for vnfr
in vnfrs
.vnfr
if
960 vm_name
in vnfr
.name
}
962 # Establish Ssh sessions to the VMs
964 for vm_name
, vm_ip
in vm_ips
.items():
965 ssh_session
= SshSession(vm_ip
)
967 assert ssh_session
.connect(username
='ubuntu', password
='ubuntu')
968 ssh_sessions
[vm_name
] = ssh_session
970 # Start python's SimpleHTTPServer on port 80 in the router VM
971 e_code
, _
, _
= ssh_sessions
['router'].run_command('sudo python -m SimpleHTTPServer 80', max_wait
=5)
972 assert e_code
is None # Due to blocking call, it should timeout and return 'None' as exit code
975 # Check: Tcp packets with dest port 80 starting from pgw VNF should go through Firewall VNF.
976 pcap_file
= 'l2test_firewall.pcap'
977 # Start tcpdump in firewall vnf and start sending tcp packets from pgw vnf
978 e_code
, _
, _
= ssh_sessions
['firewall'].run_command(
979 'sudo tcpdump -i eth1 -w {pcap} & sleep 10; sudo kill $!'.format(pcap
=pcap_file
), max_wait
=4)
980 e_code
, _
, _
= ssh_sessions
['pgw'].run_command('sudo nc {router_ip} 80 -w 0'.format(router_ip
=vm_cp_ips
['router']))
982 # Copy pcap file from firewall vnf for packet analysis
984 assert ssh_sessions
['firewall'].get(pcap_file
, pcap_file
)
985 assert pcap_analysis(pcap_file
, vm_cp_ips
['pgw'], vm_cp_ips
['router'], dst_port
=80, protocol
=TCP_PROTOCOL
)
988 # Check: Udp packets with source port 80 starting from router VNF should go through nat->dpi
989 pcap_nat
= 'l2test_nat1.pcap'
990 pcap_dpi
= 'l2test_dpi1.pcap'
991 # Start tcpdump in nat, dpi vnf and start sending udp packets from router vnf
992 e_code
, _
, _
= ssh_sessions
['nat'].run_command(
993 'sudo tcpdump -i eth1 -w {pcap} & sleep 15; sudo kill $!'.format(pcap
=pcap_nat
), max_wait
=4)
994 e_code
, _
, _
= ssh_sessions
['dpi'].run_command(
995 'sudo tcpdump -i eth1 -w {pcap} & sleep 10; sudo kill $!'.format(pcap
=pcap_dpi
), max_wait
=4)
996 e_code
, _
, _
= ssh_sessions
['router'].run_command(
997 'echo -n "hello" | sudo nc -4u {pgw_ip} 1000 -s {router_ip} -p 80 -w 0'.format(pgw_ip
=vm_cp_ips
['pgw'],
1001 # Copy pcap file from nat, dpi vnf for packet analysis
1003 assert ssh_sessions
['nat'].get(pcap_nat
, pcap_nat
)
1004 assert ssh_sessions
['dpi'].get(pcap_dpi
, pcap_dpi
)
1005 packet_ts_nat
= pcap_analysis(pcap_nat
, vm_cp_ips
['router'], vm_cp_ips
['pgw'], src_port
=80, protocol
=UDP_PROTOCOL
)
1006 packet_ts_dpi
= pcap_analysis(pcap_dpi
, vm_cp_ips
['router'], vm_cp_ips
['pgw'], src_port
=80, protocol
=UDP_PROTOCOL
)
1007 assert packet_ts_nat
1008 assert packet_ts_dpi
1009 assert packet_ts_nat
< packet_ts_dpi
# Packet flow must follow nat -> dpi
1012 # Check: Udp packets with dest port 80 starting from pgw VNF should go through dpi->nat
1013 pcap_nat
= 'l2test_nat2.pcap'
1014 pcap_dpi
= 'l2test_dpi2.pcap'
1015 # Start tcpdump in nat, dpi vnf and start sending udp packets from router vnf
1016 e_code
, _
, _
= ssh_sessions
['nat'].run_command(
1017 'sudo tcpdump -i eth1 -w {pcap} & sleep 15; sudo kill $!'.format(pcap
=pcap_nat
), max_wait
=4)
1018 e_code
, _
, _
= ssh_sessions
['dpi'].run_command(
1019 'sudo tcpdump -i eth1 -w {pcap} & sleep 10; sudo kill $!'.format(pcap
=pcap_dpi
), max_wait
=4)
1020 e_code
, _
, _
= ssh_sessions
['pgw'].run_command(
1021 'echo -n "hello" | sudo nc -4u {router_ip} 80 -w 0'.format(router_ip
=vm_cp_ips
['router']))
1023 # Copy pcap file from nat, dpi vnf for packet analysis
1025 assert ssh_sessions
['nat'].get(pcap_nat
, pcap_nat
)
1026 assert ssh_sessions
['dpi'].get(pcap_dpi
, pcap_dpi
)
1027 packet_ts_nat
= pcap_analysis(pcap_nat
, vm_cp_ips
['pgw'], vm_cp_ips
['router'], dst_port
=80, protocol
=UDP_PROTOCOL
)
1028 packet_ts_dpi
= pcap_analysis(pcap_dpi
, vm_cp_ips
['pgw'], vm_cp_ips
['router'], dst_port
=80, protocol
=UDP_PROTOCOL
)
1029 assert packet_ts_nat
1030 assert packet_ts_dpi
1031 # The below assert used to fail while testing. ts_dpi is ahead of ts_nat in few microseconds
1032 # Need to confirm if thats expected
1033 assert packet_ts_dpi
< packet_ts_nat
# Packet flow must follow dpi -> nat
1035 @pytest.mark
.depends('nsr')
1036 @pytest.mark
.setup('nfvi')
1037 @pytest.mark
.incremental
1038 class TestNfviMetrics(object):
1040 @pytest.mark
.skipif(True, reason
='NFVI metrics are disabled - RIFT-15789')
1041 def test_records_present(self
, proxy
):
1042 assert_records(proxy
)
1044 @pytest.mark
.skipif(True, reason
='NFVI metrics collected from NSR are deprecated, test needs to be updated to collected metrics from VNFRs')
1045 def test_nfvi_metrics(self
, proxy
):
1047 Verify the NFVI metrics
1050 1. Computed metrics, such as memory, cpu, storage and ports, match
1051 with the metrics in NSR record. The metrics are computed from the
1053 2. Check if the 'utilization' field has a valid value (> 0) and matches
1054 with the 'used' field, if available.
1056 for nsd
, nsr
in yield_nsd_nsr_pairs(proxy
):
1057 nfvi_metrics
= nsr
.nfvi_metrics
1058 computed_metrics
= collections
.defaultdict(int)
1060 # Get the constituent VNF records.
1061 for vnfd
, vnfr
in yield_vnfd_vnfr_pairs(proxy
, nsr
):
1063 vm_spec
= vdu
.vm_flavor
1064 computed_metrics
['vm'] += 1
1065 computed_metrics
['memory'] += vm_spec
.memory_mb
* (10**6)
1066 computed_metrics
['storage'] += vm_spec
.storage_gb
* (10**9)
1067 computed_metrics
['vcpu'] += vm_spec
.vcpu_count
1068 computed_metrics
['external_ports'] += len(vnfd
.connection_point
)
1069 computed_metrics
['internal_ports'] += len(vdu
.internal_connection_point
)
1071 assert nfvi_metrics
.vm
.active_vm
== computed_metrics
['vm']
1073 # Availability checks
1074 for metric_name
in computed_metrics
:
1075 metric_data
= getattr(nfvi_metrics
, metric_name
)
1076 total_available
= getattr(metric_data
, 'total', None)
1078 if total_available
is not None:
1079 assert computed_metrics
[metric_name
] == total_available
1081 # Utilization checks
1082 for metric_name
in ['memory', 'storage', 'vcpu']:
1083 metric_data
= getattr(nfvi_metrics
, metric_name
)
1085 utilization
= metric_data
.utilization
1086 # assert utilization > 0
1088 # If used field is available, check if it matches with utilization!
1089 total
= metric_data
.total
1090 used
= getattr(metric_data
, 'used', None)
1091 if used
is not None:
1093 computed_utilization
= round((used
/total
) * 100, 2)
1094 assert abs(computed_utilization
- utilization
) <= 0.1
1098 @pytest.mark
.depends('nfvi')
1099 @pytest.mark
.incremental
1100 @pytest.mark
.skipif(pytest
.config
.getoption("--port-sequencing"), reason
="Skip this for port-sequencing test")
1101 class TestRecordsDescriptors
:
1102 def test_create_update_vnfd(self
, proxy
, updated_ping_pong_descriptors
):
1104 Verify VNFD related operations
1107 If a VNFD record is created
1109 ping_vnfd
, pong_vnfd
, _
= updated_ping_pong_descriptors
1110 vnfdproxy
= proxy(RwProjectVnfdYang
)
1112 for vnfd
in [ping_vnfd
, pong_vnfd
]:
1113 xpath
= "/rw-project:project[rw-project:name='default']/vnfd-catalog/vnfd"
1114 vnfdproxy
.create_config(xpath
, vnfd
)
1116 xpath
= "/rw-project:project[rw-project:name='default']/vnfd-catalog/vnfd[id={}]".format(quoted_key(vnfd
.id))
1117 updated_vnfd
= vnfdproxy
.get(xpath
)
1118 assert updated_vnfd
.id == vnfd
.id
1120 vnfdproxy
.replace_config(xpath
, vnfd
)
1122 def test_create_update_nsd(self
, proxy
, updated_ping_pong_descriptors
):
1124 Verify NSD related operations
1127 If NSD record was created
1129 _
, _
, ping_pong_nsd
= updated_ping_pong_descriptors
1130 nsdproxy
= proxy(RwProjectNsdYang
)
1132 xpath
= "/rw-project:project[rw-project:name='default']/nsd-catalog/nsd"
1133 nsdproxy
.create_config(xpath
, ping_pong_nsd
)
1135 xpath
= "/rw-project:project[rw-project:name='default']/nsd-catalog/nsd[id={}]".format(quoted_key(ping_pong_nsd
.id))
1136 nsd
= nsdproxy
.get(xpath
)
1137 assert nsd
.id == ping_pong_nsd
.id
1139 nsdproxy
.replace_config(xpath
, ping_pong_nsd
)