4 # Copyright 2016 RIFT.IO Inc
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
20 @author Austin Cormier (Austin.Cormier@riftio.com)
21 @author Paul Laidler (Paul.Laidler@riftio.com)
23 @brief Launchpad System Test
39 import rift
.auto
.session
40 import rift
.mano
.examples
.ping_pong_nsd
as ping_pong
43 gi
.require_version('RwNsrYang', '1.0')
44 gi
.require_version('ProjectNsdYang', '1.0')
45 gi
.require_version('RwProjectVnfdYang', '1.0')
46 gi
.require_version('RwLaunchpadYang', '1.0')
47 gi
.require_version('RwBaseYang', '1.0')
49 from gi
.repository
import (
50 ProjectNsdYang
as NsdYang
,
56 RwProjectVnfdYang
as RwVnfdYang
,
61 logging
.basicConfig(level
=logging
.DEBUG
)
63 @pytest.fixture(scope
='module')
64 def vnfd_proxy(request
, mgmt_session
):
65 return mgmt_session
.proxy(RwVnfdYang
)
67 @pytest.fixture(scope
='module')
68 def rwvnfr_proxy(request
, mgmt_session
):
69 return mgmt_session
.proxy(RwVnfrYang
)
71 @pytest.fixture(scope
='module')
72 def vld_proxy(request
, mgmt_session
):
73 return mgmt_session
.proxy(VldYang
)
75 @pytest.fixture(scope
='module')
76 def nsd_proxy(request
, mgmt_session
):
77 return mgmt_session
.proxy(NsdYang
)
79 @pytest.fixture(scope
='module')
80 def rwnsr_proxy(request
, mgmt_session
):
81 return mgmt_session
.proxy(RwNsrYang
)
83 @pytest.fixture(scope
='module')
84 def base_proxy(request
, mgmt_session
):
85 return mgmt_session
.proxy(RwBaseYang
)
87 class DescriptorOnboardError(Exception):
90 def create_nsr(nsd
, input_param_list
, cloud_account_name
):
92 Create the NSR record object
96 input_param_list - list of input-parameter objects
97 cloud_account_name - name of cloud account
102 nsr
= RwNsrYang
.YangData_RwProject_Project_NsInstanceConfig_Nsr()
104 nsr
.id = str(uuid
.uuid4())
105 nsr
.name
= rift
.auto
.mano
.resource_name(nsr
.id)
106 nsr
.short_name
= "nsr_short_name"
107 nsr
.description
= "This is a description"
108 nsr
.nsd
.from_dict(nsr
.as_dict())
109 nsr
.admin_status
= "ENABLED"
110 nsr
.input_parameter
.extend(input_param_list
)
111 nsr
.cloud_account
= cloud_account_name
115 def upload_descriptor(logger
, descriptor_file
, host
="127.0.0.1"):
116 curl_cmd
= 'curl --insecure -F "descriptor=@{file}" https://{host}:4567/api/upload'.format(
117 file=descriptor_file
,
121 logger
.debug("Uploading descriptor %s using cmd: %s", descriptor_file
, curl_cmd
)
122 stdout
= subprocess
.check_output(shlex
.split(curl_cmd
), universal_newlines
=True)
124 json_out
= json
.loads(stdout
)
125 transaction_id
= json_out
["transaction_id"]
127 return transaction_id
129 def wait_onboard_transaction_finished(logger
, transaction_id
, timeout
=30, host
="127.0.0.1"):
131 def check_status_onboard_status():
132 uri
= 'https://%s:4567/api/upload/%s/state' % (host
, transaction_id
)
133 curl_cmd
= 'curl --insecure {uri}'.format(uri
=uri
)
134 return subprocess
.check_output(shlex
.split(curl_cmd
), universal_newlines
=True)
136 logger
.info("Waiting for onboard transaction [%s] to complete", transaction_id
)
140 while elapsed
< timeout
:
142 reply
= check_status_onboard_status()
143 state
= json
.loads(reply
)
144 if state
["status"] == "success":
147 if state
["status"] == "failure":
148 raise DescriptorOnboardError(state
["errors"])
150 if state
["status"] != "pending":
151 raise DescriptorOnboardError(state
)
154 elapsed
= time
.time() - start
157 if state
["status"] != "success":
158 raise DescriptorOnboardError(state
)
159 logger
.info("Descriptor onboard was successful")
161 def onboard_descriptor(host
, file_name
, logger
, endpoint
, scheme
, cert
):
162 """On-board/update the descriptor.
165 host (str): Launchpad IP
166 file_name (str): Full file path.
167 logger: Logger instance
168 endpoint (str): endpoint to be used for the upload operation.
171 logger
.info("Onboarding package: %s", file_name
)
172 trans_id
= upload_descriptor(
176 wait_onboard_transaction_finished(
182 def terminate_nsrs(rwvnfr_proxy
, rwnsr_proxy
, logger
):
184 Terminate the instance and check if the record is deleted.
187 1. NSR record is deleted from instance-config.
190 logger
.debug("Terminating Ping Pong NSRs")
192 nsr_path
= "/ns-instance-config"
193 nsr
= rwnsr_proxy
.get_config(nsr_path
)
197 for ping_pong
in nsrs
:
198 xpath
= "/ns-instance-config/nsr[id='{}']".format(ping_pong
.id)
199 rwnsr_proxy
.delete_config(xpath
)
204 nsr
= rwnsr_proxy
.get_config(xpath
)
207 # Get the ns-instance-config
208 ns_instance_config
= rwnsr_proxy
.get_config("/ns-instance-config")
211 vnfr
= "/vnfr-catalog/vnfr"
212 vnfrs
= rwvnfr_proxy
.get(vnfr
, list_obj
=True)
213 assert vnfrs
is None or len(vnfrs
.vnfr
) == 0
215 # nsr = "/ns-instance-opdata/nsr"
216 # nsrs = rwnsr_proxy.get(nsr, list_obj=True)
217 # assert len(nsrs.nsr) == 0
220 def generate_tar_files(tmpdir
, ping_vnfd
, pong_vnfd
, ping_pong_nsd
):
221 """Converts the descriptor to files and package them into zip files
222 that can be uploaded to LP instance.
225 tmpdir (string): Full path where the zipped files should be
226 ping_vnfd (VirtualNetworkFunction): Ping VNFD data
227 pong_vnfd (VirtualNetworkFunction): Pong VNFD data
228 ping_pong_nsd (NetworkService): PingPong NSD data
231 Tuple: file path for ping vnfd, pong vnfd and ping_pong_nsd
233 rift_build
= os
.environ
['RIFT_BUILD']
234 MANO_DIR
= os
.path
.join(
236 "modules/core/mano/src/core_mano-build/examples/ping_pong_ns")
237 ping_img
= os
.path
.join(MANO_DIR
, "ping_vnfd_with_image/images/Fedora-x86_64-20-20131211.1-sda-ping.qcow2")
238 pong_img
= os
.path
.join(MANO_DIR
, "pong_vnfd_with_image/images/Fedora-x86_64-20-20131211.1-sda-pong.qcow2")
240 """ grab cached copies of these files if not found. They may not exist
241 because our git submodule dependency mgmt
242 will not populate these because they live in .build, not .install
244 if not os
.path
.exists(ping_img
):
245 ping_img
= os
.path
.join(
246 os
.environ
['RIFT_ROOT'],
247 'images/Fedora-x86_64-20-20131211.1-sda-ping.qcow2')
248 pong_img
= os
.path
.join(
249 os
.environ
['RIFT_ROOT'],
250 'images/Fedora-x86_64-20-20131211.1-sda-pong.qcow2')
252 for descriptor
in [ping_vnfd
, pong_vnfd
, ping_pong_nsd
]:
253 descriptor
.write_to_file(output_format
='xml', outdir
=tmpdir
.name
)
255 ping_img_path
= os
.path
.join(tmpdir
.name
, "{}/images/".format(ping_vnfd
.name
))
256 pong_img_path
= os
.path
.join(tmpdir
.name
, "{}/images/".format(pong_vnfd
.name
))
257 os
.makedirs(ping_img_path
)
258 os
.makedirs(pong_img_path
)
260 shutil
.copy(ping_img
, ping_img_path
)
261 shutil
.copy(pong_img
, pong_img_path
)
263 for dir_name
in [ping_vnfd
.name
, pong_vnfd
.name
, ping_pong_nsd
.name
]:
266 "{rift_install}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh".format(rift_install
=os
.environ
['RIFT_INSTALL']),
270 return (os
.path
.join(tmpdir
.name
, "{}.tar.gz".format(ping_vnfd
.name
)),
271 os
.path
.join(tmpdir
.name
, "{}.tar.gz".format(pong_vnfd
.name
)),
272 os
.path
.join(tmpdir
.name
, "{}.tar.gz".format(ping_pong_nsd
.name
)))
275 @pytest.mark
.setup('pingpong')
276 @pytest.mark
.depends('launchpad')
277 @pytest.mark
.usefixtures('cloud_account')
278 @pytest.mark
.incremental
279 class TestPingPongStart(object):
280 """A brief overview of the steps performed.
281 1. Generate & on-board new descriptors
282 2. Start & stop the ping pong NSR
283 3. Update the exiting descriptor files.
284 4. Start the ping pong NSR.
289 def test_onboard_descriptors(
298 """Generates & On-boards the descriptors.
301 catalog
= vnfd_proxy
.get_config('/vnfd-catalog')
305 This upload routine can get called multiples times for upload API,
306 depending on the combinations of 'cloud_account' & 'endpoint'
307 fixtures. Since the records are cached at module level, we might end up
308 uploading the same uuids multiple times, thus causing errors. So a
309 simple work-around will be to skip the records when they are uploaded
312 def onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
):
314 for file_name
in [ping_vnfd_file
, pong_vnfd_file
]:
323 catalog
= vnfd_proxy
.get_config('/vnfd-catalog')
325 assert len(vnfds
) == 2, "There should two vnfds"
326 assert "ping_vnfd" in [vnfds
[0].name
, vnfds
[1].name
]
327 assert "pong_vnfd" in [vnfds
[0].name
, vnfds
[1].name
]
331 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
332 for vnfd_record
in vnfds
.vnfd
:
333 xpath
= "/vnfd-catalog/vnfd[id='{}']".format(vnfd_record
.id)
334 vnfd_proxy
.delete_config(xpath
)
337 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
338 assert vnfds
is None or len(vnfds
.vnfd
) == 0
341 if catalog
is not None and len(catalog
.vnfd
) == 2 and endpoint
== "upload":
344 if endpoint
== "update":
345 for vnfd_record
in [ping_vnfd
, pong_vnfd
]:
346 vnfd_record
.descriptor
.vnfd
[0].description
+= "_update"
347 ping_pong_nsd
.descriptor
.nsd
[0].description
+= "_update"
349 tmpdir2
= tempfile
.TemporaryDirectory()
350 temp_dirs
.append(tmpdir2
)
351 ping_pong
.generate_ping_pong_descriptors(pingcount
=1,
353 out_dir
=tmpdir2
.name
,
358 # On-board VNFDs without image
359 ping_vnfd_file
= os
.path
.join(tmpdir2
.name
, 'ping_vnfd/vnfd/ping_vnfd.json')
360 pong_vnfd_file
= os
.path
.join(tmpdir2
.name
, 'pong_vnfd/vnfd/pong_vnfd.xml')
361 onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
)
365 tmpdir
= tempfile
.TemporaryDirectory()
366 temp_dirs
.append(tmpdir
)
368 ping_vnfd
, pong_vnfd
, ping_pong_nsd
= ping_pong_records
369 ping_vnfd_file
, pong_vnfd_file
, pingpong_nsd_file
= \
370 generate_tar_files(tmpdir
, ping_vnfd
, pong_vnfd
, ping_pong_nsd
)
372 # On-board VNFDs with image
373 onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
)
384 catalog
= nsd_proxy
.get_config('/nsd-catalog')
386 assert len(nsds
) == 1, "There should only be a single nsd"
387 assert nsds
[0].name
== "ping_pong_nsd"
389 # Temp directory cleanup
390 # for temp_dir in temp_dirs:
393 def test_instantiate_ping_pong_nsr(self
, logger
, nsd_proxy
, rwnsr_proxy
, base_proxy
, cloud_account
):
395 def verify_input_parameters(running_config
, config_param
):
397 Verify the configured parameter set against the running configuration
399 for run_input_param
in running_config
.input_parameter
:
400 if (run_input_param
.xpath
== config_param
.xpath
and
401 run_input_param
.value
== config_param
.value
):
404 assert False, ("Verification of configured input parameters: { xpath:%s, value:%s} "
405 "is unsuccessful.\nRunning configuration: %s" % (config_param
.xpath
,
407 running_config
.input_parameter
))
409 catalog
= nsd_proxy
.get_config('/nsd-catalog')
412 input_parameters
= []
413 descr_xpath
= "/rw-project:project/project-nsd:nsd-catalog/project-nsd:nsd[project-nsd:id='%s']/project-nsd:vendor" % nsd
.id
414 descr_value
= "automation"
415 in_param_id
= str(uuid
.uuid4())
417 input_param_1
= NsrYang
.YangData_RwProject_Project_NsInstanceConfig_Nsr_InputParameter(
421 input_parameters
.append(input_param_1
)
423 nsr
= create_nsr(nsd
, input_parameters
, cloud_account
.name
)
425 logger
.info("Instantiating the Network Service")
426 rwnsr_proxy
.create_config('/ns-instance-config/nsr', nsr
)
428 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata/nsr[ns-instance-config-ref="{}"]'.format(nsr
.id))
429 assert nsr_opdata
is not None
431 # Verify the input parameter configuration
432 running_config
= rwnsr_proxy
.get_config("/ns-instance-config/nsr[id='%s']" % nsr
.id)
433 for input_param
in input_parameters
:
434 verify_input_parameters(running_config
, input_param
)
436 def test_wait_for_pingpong_started(self
, rwnsr_proxy
):
437 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata')
438 nsrs
= nsr_opdata
.nsr
441 xpath
= "/ns-instance-opdata/nsr[ns-instance-config-ref='{}']/operational-status".format(
442 nsr
.ns_instance_config_ref
)
443 rwnsr_proxy
.wait_for(xpath
, "running", fail_on
=['failed'], timeout
=180)
445 def test_wait_for_pingpong_configured(self
, rwnsr_proxy
):
446 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata')
447 nsrs
= nsr_opdata
.nsr
450 xpath
= "/ns-instance-opdata/nsr[ns-instance-config-ref='{}']/config-status".format(
451 nsr
.ns_instance_config_ref
)
452 rwnsr_proxy
.wait_for(xpath
, "configured", fail_on
=['failed'], timeout
=450)
455 @pytest.mark
.feature("update-api")
456 @pytest.mark
.depends('pingpong')
457 @pytest.mark
.usefixtures('cloud_account')
458 @pytest.mark
.incremental
459 class TestUpdateNsr(object):
460 def test_stop_nsr(self
, rwvnfr_proxy
, rwnsr_proxy
, logger
):
461 """Terminate the currently running NSR instance before updating the descriptor files"""
462 terminate_nsrs(rwvnfr_proxy
, rwnsr_proxy
, logger
)
464 def test_onboard_descriptors(
473 """Generates & On-boards the descriptors.
476 catalog
= vnfd_proxy
.get_config('/vnfd-catalog')
478 ping_vnfd
, pong_vnfd
, ping_pong_nsd
= ping_pong_records
481 This upload routine can get called multiples times for upload API,
482 depending on the combinations of 'cloud_account' & 'endpoint'
483 fixtures. Since the records are cached at module level, we might end up
484 uploading the same uuids multiple times, thus causing errors. So a
485 simple work-around will be to skip the records when they are uploaded
488 def onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
):
490 for file_name
in [ping_vnfd_file
, pong_vnfd_file
]:
499 catalog
= vnfd_proxy
.get_config('/vnfd-catalog')
502 assert len(vnfds
) == 2, "There should two vnfds"
503 assert "ping_vnfd" in [vnfds
[0].name
, vnfds
[1].name
]
504 assert "pong_vnfd" in [vnfds
[0].name
, vnfds
[1].name
]
507 nsds
= nsd_proxy
.get("/nsd-catalog/nsd", list_obj
=True)
508 for nsd_record
in nsds
.nsd
:
509 xpath
= "/nsd-catalog/nsd[id='{}']".format(nsd_record
.id)
510 nsd_proxy
.delete_config(xpath
)
513 nsds
= nsd_proxy
.get("/nsd-catalog/nsd", list_obj
=True)
514 assert nsds
is None or len(nsds
.nsd
) == 0
518 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
519 for vnfd_record
in vnfds
.vnfd
:
520 xpath
= "/vnfd-catalog/vnfd[id='{}']".format(vnfd_record
.id)
521 vnfd_proxy
.delete_config(xpath
)
524 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
525 assert vnfds
is None or len(vnfds
.vnfd
) == 0
529 if catalog
is not None and len(catalog
.vnfd
) == 2 and endpoint
== "upload":
532 ping_vnfd
, pong_vnfd
, ping_pong_nsd
= ping_pong_records
534 if endpoint
== "update":
535 for vnfd_record
in [ping_vnfd
, pong_vnfd
]:
536 vnfd_record
.descriptor
.vnfd
[0].description
+= "_update"
537 ping_pong_nsd
.descriptor
.nsd
[0].description
+= "_update"
539 tmpdir2
= tempfile
.TemporaryDirectory()
540 temp_dirs
.append(tmpdir2
)
541 ping_pong
.generate_ping_pong_descriptors(pingcount
=1,
543 out_dir
=tmpdir2
.name
,
548 # On-board VNFDs without image
549 ping_vnfd_file
= os
.path
.join(tmpdir2
.name
, 'ping_vnfd/vnfd/ping_vnfd.json')
550 pong_vnfd_file
= os
.path
.join(tmpdir2
.name
, 'pong_vnfd/vnfd/pong_vnfd.xml')
551 onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
)
554 tmpdir
= tempfile
.TemporaryDirectory()
555 temp_dirs
.append(tmpdir
)
557 ping_vnfd_file
, pong_vnfd_file
, pingpong_nsd_file
= \
558 generate_tar_files(tmpdir
, ping_vnfd
, pong_vnfd
, ping_pong_nsd
)
560 # On-board VNFDs with image
561 onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
)
573 catalog
= nsd_proxy
.get_config('/nsd-catalog')
575 assert len(nsds
) == 1, "There should only be a single nsd"
576 assert nsds
[0].name
== "ping_pong_nsd"
578 # Temp directory cleanup
579 # for temp_dir in temp_dirs:
582 def test_instantiate_ping_pong_nsr(self
, logger
, nsd_proxy
, rwnsr_proxy
, base_proxy
, cloud_account
):
583 def verify_input_parameters(running_config
, config_param
):
585 Verify the configured parameter set against the running configuration
587 for run_input_param
in running_config
.input_parameter
:
588 if (run_input_param
.xpath
== config_param
.xpath
and
589 run_input_param
.value
== config_param
.value
):
592 assert False, ("Verification of configured input parameters: { xpath:%s, value:%s} "
593 "is unsuccessful.\nRunning configuration: %s" % (config_param
.xpath
,
595 running_config
.input_parameter
))
597 catalog
= nsd_proxy
.get_config('/nsd-catalog')
600 input_parameters
= []
601 descr_xpath
= "/rw-project:project/project-nsd:nsd-catalog/project-nsd:nsd[project-nsd:id='%s']/project-nsd:vendor" % nsd
.id
602 descr_value
= "automation"
603 in_param_id
= str(uuid
.uuid4())
605 input_param_1
= NsrYang
.YangData_RwProject_Project_NsInstanceConfig_Nsr_InputParameter(
609 input_parameters
.append(input_param_1
)
611 nsr
= create_nsr(nsd
, input_parameters
, cloud_account
.name
)
613 logger
.info("Instantiating the Network Service")
614 rwnsr_proxy
.create_config('/ns-instance-config/nsr', nsr
)
616 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata/nsr[ns-instance-config-ref="{}"]'.format(nsr
.id))
617 assert nsr_opdata
is not None
619 # Verify the input parameter configuration
620 running_config
= rwnsr_proxy
.get_config("/ns-instance-config/nsr[id='%s']" % nsr
.id)
621 for input_param
in input_parameters
:
622 verify_input_parameters(running_config
, input_param
)
624 def test_wait_for_pingpong_started(self
, rwnsr_proxy
):
625 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata')
626 nsrs
= nsr_opdata
.nsr
629 xpath
= "/ns-instance-opdata/nsr[ns-instance-config-ref='{}']/operational-status".format(
630 nsr
.ns_instance_config_ref
)
631 rwnsr_proxy
.wait_for(xpath
, "running", fail_on
=['failed'], timeout
=180)
633 def test_wait_for_pingpong_configured(self
, rwnsr_proxy
):
634 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata')
635 nsrs
= nsr_opdata
.nsr
638 xpath
= "/ns-instance-opdata/nsr[ns-instance-config-ref='{}']/config-status".format(
639 nsr
.ns_instance_config_ref
)
640 rwnsr_proxy
.wait_for(xpath
, "configured", fail_on
=['failed'], timeout
=450)
643 @pytest.mark
.teardown('pingpong')
644 @pytest.mark
.depends('launchpad')
645 @pytest.mark
.incremental
646 class TestPingPongTeardown(object):
647 def test_terminate_nsrs(self
, rwvnfr_proxy
, rwnsr_proxy
, logger
):
649 Terminate the instance and check if the record is deleted.
652 1. NSR record is deleted from instance-config.
655 logger
.debug("Terminating Ping Pong NSR")
656 terminate_nsrs(rwvnfr_proxy
, rwnsr_proxy
, logger
)
658 def test_delete_records(self
, nsd_proxy
, vnfd_proxy
):
659 """Delete the NSD & VNFD records
662 The records are deleted.
664 nsds
= nsd_proxy
.get("/nsd-catalog/nsd", list_obj
=True)
666 xpath
= "/nsd-catalog/nsd[id='{}']".format(nsd
.id)
667 nsd_proxy
.delete_config(xpath
)
669 nsds
= nsd_proxy
.get("/nsd-catalog/nsd", list_obj
=True)
670 assert nsds
is None or len(nsds
.nsd
) == 0
672 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
673 for vnfd_record
in vnfds
.vnfd
:
674 xpath
= "/vnfd-catalog/vnfd[id='{}']".format(vnfd_record
.id)
675 vnfd_proxy
.delete_config(xpath
)
677 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
678 assert vnfds
is None or len(vnfds
.vnfd
) == 0