update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b second try
[osm/SO.git] / rwlaunchpad / ra / pytest / ns / pingpong / test_records.py
1
2 #
3 # Copyright 2016-2017 RIFT.io Inc
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16 #
17
18 import collections
19 import gi
20 import json
21 import operator
22 import os
23 import pytest
24 import re
25 import socket
26 import subprocess
27 import time
28
29 from scapy.all import rdpcap, UDP, TCP, IP
30 gi.require_version('RwNsrYang', '1.0')
31 from gi.repository import (
32 RwProjectNsdYang,
33 RwBaseYang,
34 RwConmanYang,
35 RwNsrYang,
36 RwVcsYang,
37 RwVlrYang,
38 RwProjectVnfdYang,
39 RwVnfrYang,
40 VlrYang,
41 VnfrYang,
42 NsrYang,
43 )
44 import rift.auto.mano
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
50
51
52 @pytest.fixture(scope='module')
53 def proxy(request, mgmt_session):
54 return mgmt_session.proxy
55
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
60 '''
61 return ping_pong_factory.generate_descriptors()
62
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.
67
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.
71 '''
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
77
78
79 class JobStatusError(Exception):
80 """JobStatusError."""
81
82 pass
83
84
85 def yield_vnfd_vnfr_pairs(proxy, nsr=None):
86 """
87 Yields tuples of vnfd & vnfr entries.
88
89 Args:
90 proxy (callable): Launchpad proxy
91 nsr (optional): If specified, only the vnfr & vnfd records of the NSR
92 are returned
93
94 Yields:
95 Tuple: VNFD and its corresponding VNFR entry
96 """
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)
100
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:
104
105 if nsr:
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:
108 continue
109
110 vnfd = get_vnfd(vnfr.vnfd.id)
111 yield vnfd, vnfr
112
113
114 def yield_nsd_nsr_pairs(proxy):
115 """Yields tuples of NSD & NSR
116
117 Args:
118 proxy (callable): Launchpad proxy
119
120 Yields:
121 Tuple: NSD and its corresponding NSR record
122 """
123
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)
128
129 yield nsd, nsr
130
131 def yield_nsrc_nsro_pairs(proxy):
132 """Yields tuples of NSR Config & NSR Opdata pairs
133
134 Args:
135 proxy (callable): Launchpad proxy
136
137 Yields:
138 Tuple: NSR config and its corresponding NSR op record
139 """
140 nsr = "/rw-project:project[rw-project:name='default']/ns-instance-opdata/nsr"
141 nsrs = proxy(RwNsrYang).get(nsr, list_obj=True)
142 for nsr in nsrs.nsr:
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)
146
147 yield nsr_cfg, nsr
148
149
150 def assert_records(proxy):
151 """Verifies if the NSR & VNFR records are created
152 """
153 ns_tuple = list(yield_nsd_nsr_pairs(proxy))
154 assert len(ns_tuple) == 1
155
156 vnf_tuple = list(yield_vnfd_vnfr_pairs(proxy))
157 assert len(vnf_tuple) == 2
158
159
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
167
168 Args:
169 address (str): IP address
170
171 Returns:
172 boolean
173 """
174 try:
175 socket.inet_pton(socket.AF_INET, address)
176 return True
177 except socket.error:
178 try:
179 socket.inet_pton(socket.AF_INET6, address)
180 return True
181 except socket.error:
182 return False
183
184 def is_ipv6(self, address):
185 """Returns True if address is of type 'IPv6', else False."""
186 try:
187 socket.inet_pton(socket.AF_INET6, address)
188 return True
189 except socket.error:
190 return False
191
192 @pytest.mark.feature("recovery")
193 def test_tasklets_recovery(self, mgmt_session, proxy, recover_tasklet):
194 """Test the recovery feature of tasklets
195
196 Triggers the vcrash and waits till the system is up
197 """
198 RECOVERY = "RESTART"
199
200 def vcrash(comp):
201 rpc_ip = RwVcsYang.VCrashInput.from_dict({"instance_name": comp})
202 proxy(RwVcsYang).rpc(rpc_ip)
203
204 tasklet_name = r'^{}-.*'.format(recover_tasklet)
205
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)
211
212 time.sleep(60)
213
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.
217 time.sleep(60)
218
219 def test_records_present(self, proxy):
220 assert_records(proxy)
221
222 def test_vnfd_ref_count(self, proxy):
223 """
224 Asserts
225 1. The ref count data of the VNFR with the actual number of VNFRs
226 """
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)
229
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
233
234 actual_ref_count = collections.defaultdict(int)
235 for vnfd, vnfr in yield_vnfd_vnfr_pairs(proxy):
236 actual_ref_count[vnfd.id] += 1
237
238 assert expected_ref_count == actual_ref_count
239
240 def test_nsr_nsd_records(self, proxy):
241 """
242 Verifies the correctness of the NSR record using its NSD counter-part
243
244 Asserts:
245 1. The count of vnfd and vnfr records
246 2. Count of connection point descriptor and records
247 """
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)
251
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)
256
257 def test_vdu_record_params(self, proxy):
258 """
259 Asserts:
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
263 """
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
272
273 vdur_intf_dict = {}
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
281 else:
282 vdud_intf_cp_ref = intf.external_connection_point_ref
283 assert vdur_intf_dict[intf.name] == vdud_intf_cp_ref
284
285 def test_external_vl(self, proxy):
286 """
287 Asserts:
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?
294 """
295 for vnfd, vnfr in yield_vnfd_vnfr_pairs(proxy):
296 cp_des, cp_rec = vnfd.connection_point, vnfr.connection_point
297
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
301
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)
304
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"
310
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):
313 """
314 Currently we only test for the components of NSR tests. Ignoring the
315 operational-events records
316
317 Asserts:
318 1. The constituent components.
319 2. Admin status of the corresponding NSD record.
320 """
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
325
326 assert len(nsr.constituent_vnfr_ref) == 2
327 assert nsr_cfg.admin_status == 'ENABLED'
328
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
332
333 assert len(nsrs) == 1
334 current_nsr = nsrs[0]
335
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)
338
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)
343
344 def test_vnf_monitoring_params(self, proxy):
345 """
346 Asserts:
347 1. The value counter ticks?
348 2. If the meta fields are copied over
349 """
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))
353
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)
358
359 # Meta data check
360 fields = mon_des.as_dict().keys()
361 for field in fields:
362 assert getattr(mon_des, field) == getattr(mon_rec, field)
363 # Tick check
364 #assert mon_rec.value_integer > 0
365
366 def test_ns_monitoring_params(self, logger, proxy):
367 """
368 Asserts:
369 1. monitoring-param match in nsd and ns-opdata
370 2. The value counter ticks?
371 """
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
377
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)
381
382 # Monitor the values over a period of 60 secs. Fail the test if there is no update in mon-param value.
383 s_time = time.time()
384 while (time.time() - s_time) < 60:
385 if fetch_monparam_value(nsr_ref, mon_param_id) > recent_mon_param_value:
386 return
387 time.sleep(5)
388 assert False, 'mon-param values are not getting updated. Last value was {}'.format(recent_mon_param_value)
389
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)
395
396 def test_cm_nsr(self, proxy, use_accounts):
397 """
398 Asserts:
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
403 """
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)
408
409 if not use_accounts:
410 assert con_data.name == rift.auto.mano.resource_name(nsd.name)
411
412 assert len(con_data.cm_vnfr) == 2
413
414 state_path = con_nsr_xpath + "/state"
415 proxy(RwConmanYang).wait_for(state_path, 'ready', timeout=120)
416
417 def test_cm_vnfr(self, proxy):
418 """
419 Asserts:
420 1. The ID of Vnfr in cm-state
421 2. Name of the vnfr
422 3. State of the VNFR
423 4. Checks for a reachable IP in mgmt_interface
424 5. Basic checks for connection point
425 """
426 def is_reachable(ip, timeout=10):
427 rc = subprocess.call(["ping", "-c1", "-w", str(timeout), ip])
428 if rc == 0:
429 return True
430 return False
431
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))
434
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)
438
439 assert con_data is not None
440
441 state_path = con_vnfr_path + "/state"
442 proxy(RwConmanYang).wait_for(state_path, 'ready', timeout=120)
443
444 con_data = proxy(RwConmanYang).get(con_vnfr_path)
445 assert is_reachable(con_data.mgmt_interface.ip_address) is True
446
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
456
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]
461 else:
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
466
467 @pytest.mark.skipif(
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):
471 """
472 Asserts:
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)
477 """
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))
480
481 ips = {}
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
489 else:
490 ips['static_ip'] = vnfd.vdu[0].interface[1].static_ip_address
491
492 con_vnfr_path = con_nsr_xpath + "/cm-vnfr[id={}]".format(quoted_key(vnfr.id))
493 con_data = proxy(RwConmanYang).get(con_vnfr_path)
494
495 assert con_data is not None
496 assert con_data.connection_point[1].ip_address == vnfd.vdu[0].interface[1].static_ip_address
497
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)
500
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))
504
505 addr_prop_list = vm_property['addresses'][vlr.name]
506 logger.info('addresses attribute: {}'.format(addr_prop_list))
507
508 addr_prop = [addr_prop for addr_prop in addr_prop_list if addr_prop['addr'] == vnfr.connection_point[1].ip_address]
509 assert addr_prop
510
511 assert static_ip_vnfd # if False, then none of the VNF descriptors' connections points are carrying static-ip-address field.
512
513 # Check if the VMs are reachable from each other
514 username, password = ['fedora'] * 2
515 ssh_session = SshSession(ips['mgmt_ip'])
516 assert ssh_session
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
520
521 @pytest.mark.skipif(not pytest.config.getoption("--vnf-dependencies"), reason="need --vnf-dependencies option to run")
522 def test_vnf_dependencies(self, proxy):
523 """
524 Asserts:
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.
533 """
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))
536
537 pong_source_map, ping_request_map = None, None
538
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)
543
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'
554
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)
567
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):
570 """
571 Asserts:
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'
574 """
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
577
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)
580
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]
584 assert port
585
586 port_openstack = port[0]
587 assert vnfr.connection_point[1].port_security_enabled == port_openstack['port_security_enabled']
588
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
591 else:
592 assert not port_openstack['security_groups']
593
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):
596 """
597 Asserts:
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
603 """
604 username, password = ['fedora']*2
605
606 for vnfd, vnfr in yield_vnfd_vnfr_pairs(proxy):
607 assert len(vnfd.vdu[0].interface) == len(vnfr.vdur[0].interface)
608
609 logger.debug('Interface details for vnfd {}: {}'.format(vnfd.name, vnfd.vdu[0].as_dict()['interface']))
610
611 if iteration==1:
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
615 if intf.position!=0:
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)
621
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 = {}, []
625
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]
631 else:
632 intf_without_position_field_list.append(intf[cp_ref_key])
633
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)
637
638 # Establish a ssh session to VDU to get mac address list sorted by interfaces
639 ssh_session = SshSession(vnfr.vdur[0].management_ip)
640 assert ssh_session
641 assert ssh_session.connect(username=username, password=password)
642 e_code, ip_output, err = ssh_session.run_command('sudo ip a')
643 assert e_code == 0
644 logger.debug('Output of "ip a": {}'.format(ip_output))
645 mac_addr_list = re.findall(r'link/ether\s+(.*)\s+brd', ip_output)
646
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: ]
650
651 # Get neutron port list
652 neutron_port_list = vim_clients[cloud_account_name].neutron_port_list()
653
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']]
658
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))
661
662 assert sorted_cp_names_in_vm == sorted_cp_names_in_vnfd
663
664 @pytest.mark.skipif(
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")
668 def test_primitives(
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")
675
676 rwnsr_pxy = mgmt_session.proxy(RwNsrYang)
677 nsr_pxy = mgmt_session.proxy(NsrYang)
678 rwvnfr_pxy = mgmt_session.proxy(RwVnfrYang)
679
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'
684 )
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)
689
690 # Testing a config primitive
691 vnfr_catalog = rwvnfr_pxy.get(
692 '/rw-project:project[rw-project:name="default"]' +
693 '/vnfr-catalog'
694 )
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
703 )
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
709
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'
714 )
715 counter = 0
716 counter_limit = 2
717 for idx in range(0, counter_limit):
718 if ns_opdata.config_agent_job[idx].job_status == 'failure':
719 err_msg = (
720 'Service primitive test failed.' +
721 ' The config agent reported failure job status')
722 raise JobStatusError(err_msg)
723
724 elif ns_opdata.config_agent_job[idx].job_status == 'success':
725 counter += 1
726 continue
727
728 if counter == counter_limit:
729 return True
730 else:
731 time.sleep(5)
732 return False
733
734 start_time = time.time()
735 while (time.time() - start_time < 60):
736 status = check_job_status()
737 if status:
738 break
739 else:
740 err_msg = (
741 'Service primitive test failed. Timed out: 60 seconds' +
742 'The config agent never reached a success status')
743 raise JobStatusError(err_msg)
744
745 @pytest.mark.skipif(
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):
749 """
750 Asserts:
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
758 """
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:)'
766 else:
767 assert False, 'Not expected to use this VNFD {} in this systemtest. VNFD might have changed. Exiting the test.'.format(
768 vnfd.name)
769
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)
775 time.sleep(5)
776
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))
781
782 # Establish a ssh session to VDU
783 ssh_session = SshSession(vnfr.vdur[0].management_ip)
784 assert ssh_session
785 assert ssh_session.connect(username=username, password=password)
786
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
790
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
795
796 vdur_metadata = {metadata.name: metadata.value for metadata in
797 vnfr.vdur[0].supplemental_boot_data.custom_meta_data}
798
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))
802 assert e_code == 0
803 vm_metadata = json.loads(vm_metadata)['meta']
804 logger.debug('VM metadata for {}: {}'.format(vnfd.name, vm_metadata))
805
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]
809
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):
812 """
813 This feature is only supported in openstack, brocade vCPE.
814 Asserts:
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.
822 """
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
826
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
830
831 # Assert 1: Match volumes in vnfd, vnfr, test data
832 assert len(vnfd.vdu[0].volumes) == len(vnfr.vdur[0].volumes)
833
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]
837
838 vol_testdata = vnf_testdata[vnfr_vol.name]
839
840 for i, attr in enumerate(vol_attr):
841 if attr == None: # device_bus doesn't appear in vnfr/vdur
842 continue
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')
847 continue
848
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]
852
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))
858
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))
862
863 assert vim_volumes
864 assert len(vim_volumes) == len(vnfr.vdur[0].volumes)
865
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']]
872
873 # Get volume details. Equivalent cli: openstack volume show <uuid>
874 vim_vol_attrs = vim_client.cinder_volume_get(vim_volume['id'])
875
876 # Match volume size
877 assert vnfr_vol_.size == vim_vol_attrs.size
878
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']
883 else:
884 assert not hasattr(vim_vol_attrs, 'volume_image_metadata')
885
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']]
889
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):
892 """
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.
895 Asserts:
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
904
905 """
906 UDP_PROTOCOL, TCP_PROTOCOL = 17, 6
907
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.
910 Args:
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)
914
915 Returns:
916 timestamp of the packet which is matched (Needed to check packet flow order through VNFs)
917 or
918 False: if there is no packet match
919
920 It uses scapy module to analyse pcap file. pip3 install scapy-python3
921 Other options https://pypi.python.org/pypi/pypcapfile
922 """
923 assert os.path.exists(pcap_file)
924 pkt_type = TCP if protocol==6 else UDP
925
926 pcap_obj = rdpcap(pcap_file)
927 for pkt in pcap_obj:
928 if IP in pkt:
929 if not(pkt[IP].src==src_add and pkt[IP].dst==dst_add and pkt[IP].proto==protocol):
930 continue
931 if pkt_type in pkt:
932 if src_port:
933 if not (pkt[pkt_type].sport==src_port):
934 continue
935 if dst_port:
936 if not (pkt[pkt_type].dport==dst_port):
937 continue
938 return pkt[IP].time
939 return False
940
941 # Check the VNFFG in nsd and nsr
942 for nsd, nsr in yield_nsd_nsr_pairs(proxy):
943 vnffgds = nsd.vnffgd
944 vnffgrs = nsr.vnffgr
945 assert len(vnffgds) == len(vnffgrs)
946
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)
952
953 vnfrs = proxy(RwVnfrYang).get('/rw-project:project[rw-project:name="default"]/vnfr-catalog/vnfr', list_obj=True)
954
955 # Get the IP of VMs
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}
961
962 # Establish Ssh sessions to the VMs
963 ssh_sessions = {}
964 for vm_name, vm_ip in vm_ips.items():
965 ssh_session = SshSession(vm_ip)
966 assert ssh_session
967 assert ssh_session.connect(username='ubuntu', password='ubuntu')
968 ssh_sessions[vm_name] = ssh_session
969
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
973
974
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']))
981
982 # Copy pcap file from firewall vnf for packet analysis
983 time.sleep(10)
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)
986
987
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'],
998 router_ip=vm_cp_ips[
999 'router']))
1000
1001 # Copy pcap file from nat, dpi vnf for packet analysis
1002 time.sleep(10)
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
1010
1011
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']))
1022
1023 # Copy pcap file from nat, dpi vnf for packet analysis
1024 time.sleep(10)
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
1034
1035 @pytest.mark.depends('nsr')
1036 @pytest.mark.setup('nfvi')
1037 @pytest.mark.incremental
1038 class TestNfviMetrics(object):
1039
1040 @pytest.mark.skipif(True, reason='NFVI metrics are disabled - RIFT-15789')
1041 def test_records_present(self, proxy):
1042 assert_records(proxy)
1043
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):
1046 """
1047 Verify the NFVI metrics
1048
1049 Asserts:
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
1052 descriptor records.
1053 2. Check if the 'utilization' field has a valid value (> 0) and matches
1054 with the 'used' field, if available.
1055 """
1056 for nsd, nsr in yield_nsd_nsr_pairs(proxy):
1057 nfvi_metrics = nsr.nfvi_metrics
1058 computed_metrics = collections.defaultdict(int)
1059
1060 # Get the constituent VNF records.
1061 for vnfd, vnfr in yield_vnfd_vnfr_pairs(proxy, nsr):
1062 vdu = vnfd.vdu[0]
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)
1070
1071 assert nfvi_metrics.vm.active_vm == computed_metrics['vm']
1072
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)
1077
1078 if total_available is not None:
1079 assert computed_metrics[metric_name] == total_available
1080
1081 # Utilization checks
1082 for metric_name in ['memory', 'storage', 'vcpu']:
1083 metric_data = getattr(nfvi_metrics, metric_name)
1084
1085 utilization = metric_data.utilization
1086 # assert utilization > 0
1087
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:
1092 assert total > 0
1093 computed_utilization = round((used/total) * 100, 2)
1094 assert abs(computed_utilization - utilization) <= 0.1
1095
1096
1097
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):
1103 """
1104 Verify VNFD related operations
1105
1106 Asserts:
1107 If a VNFD record is created
1108 """
1109 ping_vnfd, pong_vnfd, _ = updated_ping_pong_descriptors
1110 vnfdproxy = proxy(RwProjectVnfdYang)
1111
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)
1115
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
1119
1120 vnfdproxy.replace_config(xpath, vnfd)
1121
1122 def test_create_update_nsd(self, proxy, updated_ping_pong_descriptors):
1123 """
1124 Verify NSD related operations
1125
1126 Asserts:
1127 If NSD record was created
1128 """
1129 _, _, ping_pong_nsd = updated_ping_pong_descriptors
1130 nsdproxy = proxy(RwProjectNsdYang)
1131
1132 xpath = "/rw-project:project[rw-project:name='default']/nsd-catalog/nsd"
1133 nsdproxy.create_config(xpath, ping_pong_nsd)
1134
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
1138
1139 nsdproxy.replace_config(xpath, ping_pong_nsd)
1140