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('RwVnfdYang', '1.0')
45 gi
.require_version('RwLaunchpadYang', '1.0')
46 gi
.require_version('RwBaseYang', '1.0')
48 from gi
.repository
import (
60 logging
.basicConfig(level
=logging
.DEBUG
)
62 @pytest.fixture(scope
='module')
63 def vnfd_proxy(request
, mgmt_session
):
64 return mgmt_session
.proxy(RwVnfdYang
)
66 @pytest.fixture(scope
='module')
67 def rwvnfr_proxy(request
, mgmt_session
):
68 return mgmt_session
.proxy(RwVnfrYang
)
70 @pytest.fixture(scope
='module')
71 def vld_proxy(request
, mgmt_session
):
72 return mgmt_session
.proxy(VldYang
)
74 @pytest.fixture(scope
='module')
75 def nsd_proxy(request
, mgmt_session
):
76 return mgmt_session
.proxy(NsdYang
)
78 @pytest.fixture(scope
='module')
79 def rwnsr_proxy(request
, mgmt_session
):
80 return mgmt_session
.proxy(RwNsrYang
)
82 @pytest.fixture(scope
='module')
83 def base_proxy(request
, mgmt_session
):
84 return mgmt_session
.proxy(RwBaseYang
)
86 class DescriptorOnboardError(Exception):
89 def create_nsr(nsd
, input_param_list
, cloud_account_name
):
91 Create the NSR record object
95 input_param_list - list of input-parameter objects
96 cloud_account_name - name of cloud account
101 nsr
= RwNsrYang
.YangData_Nsr_NsInstanceConfig_Nsr()
103 nsr
.id = str(uuid
.uuid4())
104 nsr
.name
= rift
.auto
.mano
.resource_name(nsr
.id)
105 nsr
.short_name
= "nsr_short_name"
106 nsr
.description
= "This is a description"
107 nsr
.nsd
.from_dict(nsr
.as_dict())
108 nsr
.admin_status
= "ENABLED"
109 nsr
.input_parameter
.extend(input_param_list
)
110 nsr
.cloud_account
= cloud_account_name
114 def upload_descriptor(logger
, descriptor_file
, host
="127.0.0.1"):
115 curl_cmd
= 'curl --insecure -F "descriptor=@{file}" https://{host}:4567/api/upload'.format(
116 file=descriptor_file
,
120 logger
.debug("Uploading descriptor %s using cmd: %s", descriptor_file
, curl_cmd
)
121 stdout
= subprocess
.check_output(shlex
.split(curl_cmd
), universal_newlines
=True)
123 json_out
= json
.loads(stdout
)
124 transaction_id
= json_out
["transaction_id"]
126 return transaction_id
128 def wait_onboard_transaction_finished(logger
, transaction_id
, timeout
=30, host
="127.0.0.1"):
130 def check_status_onboard_status():
131 uri
= 'https://%s:4567/api/upload/%s/state' % (host
, transaction_id
)
132 curl_cmd
= 'curl --insecure {uri}'.format(uri
=uri
)
133 return subprocess
.check_output(shlex
.split(curl_cmd
), universal_newlines
=True)
135 logger
.info("Waiting for onboard transaction [%s] to complete", transaction_id
)
139 while elapsed
< timeout
:
141 reply
= check_status_onboard_status()
142 state
= json
.loads(reply
)
143 if state
["status"] == "success":
146 if state
["status"] == "failure":
147 raise DescriptorOnboardError(state
["errors"])
149 if state
["status"] != "pending":
150 raise DescriptorOnboardError(state
)
153 elapsed
= time
.time() - start
156 if state
["status"] != "success":
157 raise DescriptorOnboardError(state
)
158 logger
.info("Descriptor onboard was successful")
160 def onboard_descriptor(host
, file_name
, logger
, endpoint
, scheme
, cert
):
161 """On-board/update the descriptor.
164 host (str): Launchpad IP
165 file_name (str): Full file path.
166 logger: Logger instance
167 endpoint (str): endpoint to be used for the upload operation.
170 logger
.info("Onboarding package: %s", file_name
)
171 trans_id
= upload_descriptor(
175 wait_onboard_transaction_finished(
181 def terminate_nsrs(rwvnfr_proxy
, rwnsr_proxy
, logger
):
183 Terminate the instance and check if the record is deleted.
186 1. NSR record is deleted from instance-config.
189 logger
.debug("Terminating Ping Pong NSRs")
191 nsr_path
= "/ns-instance-config"
192 nsr
= rwnsr_proxy
.get_config(nsr_path
)
196 for ping_pong
in nsrs
:
197 xpath
= "/ns-instance-config/nsr[id='{}']".format(ping_pong
.id)
198 rwnsr_proxy
.delete_config(xpath
)
203 nsr
= rwnsr_proxy
.get_config(xpath
)
206 # Get the ns-instance-config
207 ns_instance_config
= rwnsr_proxy
.get_config("/ns-instance-config")
210 vnfr
= "/vnfr-catalog/vnfr"
211 vnfrs
= rwvnfr_proxy
.get(vnfr
, list_obj
=True)
212 assert vnfrs
is None or len(vnfrs
.vnfr
) == 0
214 # nsr = "/ns-instance-opdata/nsr"
215 # nsrs = rwnsr_proxy.get(nsr, list_obj=True)
216 # assert len(nsrs.nsr) == 0
219 def generate_tar_files(tmpdir
, ping_vnfd
, pong_vnfd
, ping_pong_nsd
):
220 """Converts the descriptor to files and package them into zip files
221 that can be uploaded to LP instance.
224 tmpdir (string): Full path where the zipped files should be
225 ping_vnfd (VirtualNetworkFunction): Ping VNFD data
226 pong_vnfd (VirtualNetworkFunction): Pong VNFD data
227 ping_pong_nsd (NetworkService): PingPong NSD data
230 Tuple: file path for ping vnfd, pong vnfd and ping_pong_nsd
232 rift_build
= os
.environ
['RIFT_BUILD']
233 MANO_DIR
= os
.path
.join(
235 "modules/core/mano/src/core_mano-build/examples/ping_pong_ns")
236 ping_img
= os
.path
.join(MANO_DIR
, "ping_vnfd_with_image/images/Fedora-x86_64-20-20131211.1-sda-ping.qcow2")
237 pong_img
= os
.path
.join(MANO_DIR
, "pong_vnfd_with_image/images/Fedora-x86_64-20-20131211.1-sda-pong.qcow2")
239 """ grab cached copies of these files if not found. They may not exist
240 because our git submodule dependency mgmt
241 will not populate these because they live in .build, not .install
243 if not os
.path
.exists(ping_img
):
244 ping_img
= os
.path
.join(
245 os
.environ
['RIFT_ROOT'],
246 'images/Fedora-x86_64-20-20131211.1-sda-ping.qcow2')
247 pong_img
= os
.path
.join(
248 os
.environ
['RIFT_ROOT'],
249 'images/Fedora-x86_64-20-20131211.1-sda-pong.qcow2')
251 for descriptor
in [ping_vnfd
, pong_vnfd
, ping_pong_nsd
]:
252 descriptor
.write_to_file(output_format
='xml', outdir
=tmpdir
.name
)
254 ping_img_path
= os
.path
.join(tmpdir
.name
, "{}/images/".format(ping_vnfd
.name
))
255 pong_img_path
= os
.path
.join(tmpdir
.name
, "{}/images/".format(pong_vnfd
.name
))
256 os
.makedirs(ping_img_path
)
257 os
.makedirs(pong_img_path
)
259 shutil
.copy(ping_img
, ping_img_path
)
260 shutil
.copy(pong_img
, pong_img_path
)
262 for dir_name
in [ping_vnfd
.name
, pong_vnfd
.name
, ping_pong_nsd
.name
]:
265 "{rift_install}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh".format(rift_install
=os
.environ
['RIFT_INSTALL']),
269 return (os
.path
.join(tmpdir
.name
, "{}.tar.gz".format(ping_vnfd
.name
)),
270 os
.path
.join(tmpdir
.name
, "{}.tar.gz".format(pong_vnfd
.name
)),
271 os
.path
.join(tmpdir
.name
, "{}.tar.gz".format(ping_pong_nsd
.name
)))
274 @pytest.mark
.setup('pingpong')
275 @pytest.mark
.depends('launchpad')
276 @pytest.mark
.usefixtures('cloud_account')
277 @pytest.mark
.incremental
278 class TestPingPongStart(object):
279 """A brief overview of the steps performed.
280 1. Generate & on-board new descriptors
281 2. Start & stop the ping pong NSR
282 3. Update the exiting descriptor files.
283 4. Start the ping pong NSR.
288 def test_onboard_descriptors(
297 """Generates & On-boards the descriptors.
300 catalog
= vnfd_proxy
.get_config('/vnfd-catalog')
304 This upload routine can get called multiples times for upload API,
305 depending on the combinations of 'cloud_account' & 'endpoint'
306 fixtures. Since the records are cached at module level, we might end up
307 uploading the same uuids multiple times, thus causing errors. So a
308 simple work-around will be to skip the records when they are uploaded
311 def onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
):
313 for file_name
in [ping_vnfd_file
, pong_vnfd_file
]:
322 catalog
= vnfd_proxy
.get_config('/vnfd-catalog')
324 assert len(vnfds
) == 2, "There should two vnfds"
325 assert "ping_vnfd" in [vnfds
[0].name
, vnfds
[1].name
]
326 assert "pong_vnfd" in [vnfds
[0].name
, vnfds
[1].name
]
330 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
331 for vnfd_record
in vnfds
.vnfd
:
332 xpath
= "/vnfd-catalog/vnfd[id='{}']".format(vnfd_record
.id)
333 vnfd_proxy
.delete_config(xpath
)
336 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
337 assert vnfds
is None or len(vnfds
.vnfd
) == 0
340 if catalog
is not None and len(catalog
.vnfd
) == 2 and endpoint
== "upload":
343 if endpoint
== "update":
344 for vnfd_record
in [ping_vnfd
, pong_vnfd
]:
345 vnfd_record
.descriptor
.vnfd
[0].description
+= "_update"
346 ping_pong_nsd
.descriptor
.nsd
[0].description
+= "_update"
348 tmpdir2
= tempfile
.TemporaryDirectory()
349 temp_dirs
.append(tmpdir2
)
350 ping_pong
.generate_ping_pong_descriptors(pingcount
=1,
352 out_dir
=tmpdir2
.name
,
357 # On-board VNFDs without image
358 ping_vnfd_file
= os
.path
.join(tmpdir2
.name
, 'ping_vnfd/vnfd/ping_vnfd.json')
359 pong_vnfd_file
= os
.path
.join(tmpdir2
.name
, 'pong_vnfd/vnfd/pong_vnfd.xml')
360 onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
)
364 tmpdir
= tempfile
.TemporaryDirectory()
365 temp_dirs
.append(tmpdir
)
367 ping_vnfd
, pong_vnfd
, ping_pong_nsd
= ping_pong_records
368 ping_vnfd_file
, pong_vnfd_file
, pingpong_nsd_file
= \
369 generate_tar_files(tmpdir
, ping_vnfd
, pong_vnfd
, ping_pong_nsd
)
371 # On-board VNFDs with image
372 onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
)
383 catalog
= nsd_proxy
.get_config('/nsd-catalog')
385 assert len(nsds
) == 1, "There should only be a single nsd"
386 assert nsds
[0].name
== "ping_pong_nsd"
388 # Temp directory cleanup
389 # for temp_dir in temp_dirs:
392 def test_instantiate_ping_pong_nsr(self
, logger
, nsd_proxy
, rwnsr_proxy
, base_proxy
, cloud_account
):
394 def verify_input_parameters(running_config
, config_param
):
396 Verify the configured parameter set against the running configuration
398 for run_input_param
in running_config
.input_parameter
:
399 if (run_input_param
.xpath
== config_param
.xpath
and
400 run_input_param
.value
== config_param
.value
):
403 assert False, ("Verification of configured input parameters: { xpath:%s, value:%s} "
404 "is unsuccessful.\nRunning configuration: %s" % (config_param
.xpath
,
406 running_config
.input_parameter
))
408 catalog
= nsd_proxy
.get_config('/nsd-catalog')
411 input_parameters
= []
412 descr_xpath
= "/nsd:nsd-catalog/nsd:nsd[nsd:id='%s']/nsd:vendor" % nsd
.id
413 descr_value
= "automation"
414 in_param_id
= str(uuid
.uuid4())
416 input_param_1
= NsrYang
.YangData_Nsr_NsInstanceConfig_Nsr_InputParameter(
420 input_parameters
.append(input_param_1
)
422 nsr
= create_nsr(nsd
, input_parameters
, cloud_account
.name
)
424 logger
.info("Instantiating the Network Service")
425 rwnsr_proxy
.create_config('/ns-instance-config/nsr', nsr
)
427 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata/nsr[ns-instance-config-ref="{}"]'.format(nsr
.id))
428 assert nsr_opdata
is not None
430 # Verify the input parameter configuration
431 running_config
= rwnsr_proxy
.get_config("/ns-instance-config/nsr[id='%s']" % nsr
.id)
432 for input_param
in input_parameters
:
433 verify_input_parameters(running_config
, input_param
)
435 def test_wait_for_pingpong_started(self
, rwnsr_proxy
):
436 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata')
437 nsrs
= nsr_opdata
.nsr
440 xpath
= "/ns-instance-opdata/nsr[ns-instance-config-ref='{}']/operational-status".format(
441 nsr
.ns_instance_config_ref
)
442 rwnsr_proxy
.wait_for(xpath
, "running", fail_on
=['failed'], timeout
=180)
444 def test_wait_for_pingpong_configured(self
, rwnsr_proxy
):
445 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata')
446 nsrs
= nsr_opdata
.nsr
449 xpath
= "/ns-instance-opdata/nsr[ns-instance-config-ref='{}']/config-status".format(
450 nsr
.ns_instance_config_ref
)
451 rwnsr_proxy
.wait_for(xpath
, "configured", fail_on
=['failed'], timeout
=450)
454 @pytest.mark
.feature("update-api")
455 @pytest.mark
.depends('pingpong')
456 @pytest.mark
.usefixtures('cloud_account')
457 @pytest.mark
.incremental
458 class TestUpdateNsr(object):
459 def test_stop_nsr(self
, rwvnfr_proxy
, rwnsr_proxy
, logger
):
460 """Terminate the currently running NSR instance before updating the descriptor files"""
461 terminate_nsrs(rwvnfr_proxy
, rwnsr_proxy
, logger
)
463 def test_onboard_descriptors(
472 """Generates & On-boards the descriptors.
475 catalog
= vnfd_proxy
.get_config('/vnfd-catalog')
477 ping_vnfd
, pong_vnfd
, ping_pong_nsd
= ping_pong_records
480 This upload routine can get called multiples times for upload API,
481 depending on the combinations of 'cloud_account' & 'endpoint'
482 fixtures. Since the records are cached at module level, we might end up
483 uploading the same uuids multiple times, thus causing errors. So a
484 simple work-around will be to skip the records when they are uploaded
487 def onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
):
489 for file_name
in [ping_vnfd_file
, pong_vnfd_file
]:
498 catalog
= vnfd_proxy
.get_config('/vnfd-catalog')
501 assert len(vnfds
) == 2, "There should two vnfds"
502 assert "ping_vnfd" in [vnfds
[0].name
, vnfds
[1].name
]
503 assert "pong_vnfd" in [vnfds
[0].name
, vnfds
[1].name
]
506 nsds
= nsd_proxy
.get("/nsd-catalog/nsd", list_obj
=True)
507 for nsd_record
in nsds
.nsd
:
508 xpath
= "/nsd-catalog/nsd[id='{}']".format(nsd_record
.id)
509 nsd_proxy
.delete_config(xpath
)
512 nsds
= nsd_proxy
.get("/nsd-catalog/nsd", list_obj
=True)
513 assert nsds
is None or len(nsds
.nsd
) == 0
517 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
518 for vnfd_record
in vnfds
.vnfd
:
519 xpath
= "/vnfd-catalog/vnfd[id='{}']".format(vnfd_record
.id)
520 vnfd_proxy
.delete_config(xpath
)
523 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
524 assert vnfds
is None or len(vnfds
.vnfd
) == 0
528 if catalog
is not None and len(catalog
.vnfd
) == 2 and endpoint
== "upload":
531 ping_vnfd
, pong_vnfd
, ping_pong_nsd
= ping_pong_records
533 if endpoint
== "update":
534 for vnfd_record
in [ping_vnfd
, pong_vnfd
]:
535 vnfd_record
.descriptor
.vnfd
[0].description
+= "_update"
536 ping_pong_nsd
.descriptor
.nsd
[0].description
+= "_update"
538 tmpdir2
= tempfile
.TemporaryDirectory()
539 temp_dirs
.append(tmpdir2
)
540 ping_pong
.generate_ping_pong_descriptors(pingcount
=1,
542 out_dir
=tmpdir2
.name
,
547 # On-board VNFDs without image
548 ping_vnfd_file
= os
.path
.join(tmpdir2
.name
, 'ping_vnfd/vnfd/ping_vnfd.json')
549 pong_vnfd_file
= os
.path
.join(tmpdir2
.name
, 'pong_vnfd/vnfd/pong_vnfd.xml')
550 onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
)
553 tmpdir
= tempfile
.TemporaryDirectory()
554 temp_dirs
.append(tmpdir
)
556 ping_vnfd_file
, pong_vnfd_file
, pingpong_nsd_file
= \
557 generate_tar_files(tmpdir
, ping_vnfd
, pong_vnfd
, ping_pong_nsd
)
559 # On-board VNFDs with image
560 onboard_ping_pong_vnfds(ping_vnfd_file
, pong_vnfd_file
)
572 catalog
= nsd_proxy
.get_config('/nsd-catalog')
574 assert len(nsds
) == 1, "There should only be a single nsd"
575 assert nsds
[0].name
== "ping_pong_nsd"
577 # Temp directory cleanup
578 # for temp_dir in temp_dirs:
581 def test_instantiate_ping_pong_nsr(self
, logger
, nsd_proxy
, rwnsr_proxy
, base_proxy
, cloud_account
):
582 def verify_input_parameters(running_config
, config_param
):
584 Verify the configured parameter set against the running configuration
586 for run_input_param
in running_config
.input_parameter
:
587 if (run_input_param
.xpath
== config_param
.xpath
and
588 run_input_param
.value
== config_param
.value
):
591 assert False, ("Verification of configured input parameters: { xpath:%s, value:%s} "
592 "is unsuccessful.\nRunning configuration: %s" % (config_param
.xpath
,
594 running_config
.input_parameter
))
596 catalog
= nsd_proxy
.get_config('/nsd-catalog')
599 input_parameters
= []
600 descr_xpath
= "/nsd:nsd-catalog/nsd:nsd[nsd:id='%s']/nsd:vendor" % nsd
.id
601 descr_value
= "automation"
602 in_param_id
= str(uuid
.uuid4())
604 input_param_1
= NsrYang
.YangData_Nsr_NsInstanceConfig_Nsr_InputParameter(
608 input_parameters
.append(input_param_1
)
610 nsr
= create_nsr(nsd
, input_parameters
, cloud_account
.name
)
612 logger
.info("Instantiating the Network Service")
613 rwnsr_proxy
.create_config('/ns-instance-config/nsr', nsr
)
615 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata/nsr[ns-instance-config-ref="{}"]'.format(nsr
.id))
616 assert nsr_opdata
is not None
618 # Verify the input parameter configuration
619 running_config
= rwnsr_proxy
.get_config("/ns-instance-config/nsr[id='%s']" % nsr
.id)
620 for input_param
in input_parameters
:
621 verify_input_parameters(running_config
, input_param
)
623 def test_wait_for_pingpong_started(self
, rwnsr_proxy
):
624 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata')
625 nsrs
= nsr_opdata
.nsr
628 xpath
= "/ns-instance-opdata/nsr[ns-instance-config-ref='{}']/operational-status".format(
629 nsr
.ns_instance_config_ref
)
630 rwnsr_proxy
.wait_for(xpath
, "running", fail_on
=['failed'], timeout
=180)
632 def test_wait_for_pingpong_configured(self
, rwnsr_proxy
):
633 nsr_opdata
= rwnsr_proxy
.get('/ns-instance-opdata')
634 nsrs
= nsr_opdata
.nsr
637 xpath
= "/ns-instance-opdata/nsr[ns-instance-config-ref='{}']/config-status".format(
638 nsr
.ns_instance_config_ref
)
639 rwnsr_proxy
.wait_for(xpath
, "configured", fail_on
=['failed'], timeout
=450)
642 @pytest.mark
.teardown('pingpong')
643 @pytest.mark
.depends('launchpad')
644 @pytest.mark
.incremental
645 class TestPingPongTeardown(object):
646 def test_terminate_nsrs(self
, rwvnfr_proxy
, rwnsr_proxy
, logger
):
648 Terminate the instance and check if the record is deleted.
651 1. NSR record is deleted from instance-config.
654 logger
.debug("Terminating Ping Pong NSR")
655 terminate_nsrs(rwvnfr_proxy
, rwnsr_proxy
, logger
)
657 def test_delete_records(self
, nsd_proxy
, vnfd_proxy
):
658 """Delete the NSD & VNFD records
661 The records are deleted.
663 nsds
= nsd_proxy
.get("/nsd-catalog/nsd", list_obj
=True)
665 xpath
= "/nsd-catalog/nsd[id='{}']".format(nsd
.id)
666 nsd_proxy
.delete_config(xpath
)
668 nsds
= nsd_proxy
.get("/nsd-catalog/nsd", list_obj
=True)
669 assert nsds
is None or len(nsds
.nsd
) == 0
671 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
672 for vnfd_record
in vnfds
.vnfd
:
673 xpath
= "/vnfd-catalog/vnfd[id='{}']".format(vnfd_record
.id)
674 vnfd_proxy
.delete_config(xpath
)
676 vnfds
= vnfd_proxy
.get("/vnfd-catalog/vnfd", list_obj
=True)
677 assert vnfds
is None or len(vnfds
.vnfd
) == 0