Support ChangeVNFPackage command in tackerclient

Add ``openstack vnflcm change_vnfpkg`` to python-tackerclient. This
command can execute change current vnf package operation [1].

[1]
https://www.etsi.org/deliver/etsi_gs/NFV-SOL/001_099/003/03.03.01_60/gs_NFV-SOL003v030301p.pdf

Implements: blueprint upgrade-vnf-package
Change-Id: I0d9c88147dee3a273cb27224db6be6c91cf7fe55
This commit is contained in:
Yi Feng 2022-01-25 17:09:15 +09:00
parent b7f27c3dc6
commit ab901b6466
7 changed files with 233 additions and 0 deletions

View File

@ -110,6 +110,7 @@ openstack.tackerclient.v2 =
vnflcm_list = tackerclient.osc.v1.vnflcm.vnflcm:ListVnfLcm vnflcm_list = tackerclient.osc.v1.vnflcm.vnflcm:ListVnfLcm
vnflcm_instantiate = tackerclient.osc.v1.vnflcm.vnflcm:InstantiateVnfLcm vnflcm_instantiate = tackerclient.osc.v1.vnflcm.vnflcm:InstantiateVnfLcm
vnflcm_terminate = tackerclient.osc.v1.vnflcm.vnflcm:TerminateVnfLcm vnflcm_terminate = tackerclient.osc.v1.vnflcm.vnflcm:TerminateVnfLcm
vnflcm_change-vnfpkg = tackerclient.osc.v1.vnflcm.vnflcm:ChangeVnfPkgVnfLcm
vnflcm_delete = tackerclient.osc.v1.vnflcm.vnflcm:DeleteVnfLcm vnflcm_delete = tackerclient.osc.v1.vnflcm.vnflcm:DeleteVnfLcm
vnflcm_op_list = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ListVnfLcmOp vnflcm_op_list = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ListVnfLcmOp
vnflcm_op_show = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ShowVnfLcmOp vnflcm_op_show = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ShowVnfLcmOp

View File

@ -207,6 +207,10 @@ class InvalidInput(TackerClientException):
message = _("Invalid input: %(reason)s") message = _("Invalid input: %(reason)s")
class UnsupportedCommandVersion(TackerClientException):
message = _("This command is not supported in version %(version)s")
# Command line exceptions # Command line exceptions
class TackerCLIError(TackerException): class TackerCLIError(TackerException):

View File

@ -566,3 +566,30 @@ class ChangeExtConnVnfLcm(command.Command):
if not result: if not result:
print((_('Change External VNF Connectivity for VNF Instance %s ' print((_('Change External VNF Connectivity for VNF Instance %s '
'has been accepted.') % parsed_args.vnf_instance)) 'has been accepted.') % parsed_args.vnf_instance))
class ChangeVnfPkgVnfLcm(command.Command):
_description = _("Change Current VNF Package")
def get_parser(self, prog_name):
parser = super(ChangeVnfPkgVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_("VNF instance ID to Change Current VNF Package"))
parser.add_argument(
'request_file',
metavar="<param-file>",
help=_("Specify change-vnfpkg request parameters "
"in a json file."))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.change_vnfpkg_vnf_instance(
parsed_args.vnf_instance, jsonfile2body(
parsed_args.request_file))
if not result:
print((_('Change Current VNF Package for VNF Instance %s '
'has been accepted.') % parsed_args.vnf_instance))

View File

@ -0,0 +1,36 @@
{
"vnfdId": "c6595341-a5bb-8246-53c4-7aeb843d60c5",
"additionalParams": {
"upgrade_type": "RollingUpdate",
"lcm-operation-coordinate-old-vnf": "./Scripts/coordinate_old_vnf.py",
"lcm-operation-coordinate-old-vnf-class": "CoordinateOldVnf",
"lcm-operation-coordinate-new-vnf": "./Scripts/coordinate_new_vnf.py",
"lcm-operation-coordinate-new-vnf-class": "CoordinateNewVnf",
"vdu_params": [{
"vduId": "VDU1",
"old_vnfc_param": {
"cp_name": "VDU1_CP1",
"username": "ubuntu",
"password": "ubuntu"
},
"new_vnfc_param": {
"cp_name": "VDU1_CP1",
"username": "ubuntu",
"password": "ubuntu"
}
}, {
"vduId": "VDU2",
"old_vnfc_param": {
"cp_name": "VDU2_CP1",
"username": "ubuntu",
"password": "ubuntu"
},
"new_vnfc_param": {
"cp_name": "VDU2_CP1",
"username": "ubuntu",
"password": "ubuntu"
}
}]
}
}

View File

@ -850,6 +850,40 @@ class TestChangeExtConnVnfLcm(TestVnfLcm):
self.assertIn(expected_msg, str(ex)) self.assertIn(expected_msg, str(ex))
class TestChangeVnfPkgVnfLcm(TestVnfLcm):
def setUp(self):
super(TestChangeVnfPkgVnfLcm, self).setUp()
self.change_vnfpkg_vnf_lcm = vnflcm.ChangeVnfPkgVnfLcm(
self.app, self.app_args,
cmd_name='vnflcm change-vnfpkg')
def test_take_action_with_v1_version(self):
vnf_instance = vnflcm_fakes.vnf_instance_response()
sample_param_file = ("./tackerclient/osc/v2/vnflcm/samples/"
"change_vnfpkg_vnf_instance_param_sample.json")
arglist = [vnf_instance['id'], sample_param_file]
verifylist = [('vnf_instance', vnf_instance['id']),
('request_file', sample_param_file)]
# command param
parsed_args = self.check_parser(self.change_vnfpkg_vnf_lcm,
arglist,
verifylist)
url = os.path.join(self.url, 'vnflcm/v1/vnf_instances',
vnf_instance['id'], 'change_vnfpkg')
self.requests_mock.register_uri(
'POST', url, headers=self.header, status_code=400, json={})
ex = self.assertRaises(exceptions.UnsupportedCommandVersion,
self.change_vnfpkg_vnf_lcm.take_action,
parsed_args)
expected_msg = "This command is not supported in version 1"
self.assertEqual(expected_msg, str(ex))
class TestVnfLcmV1(base.FixturedTestCase): class TestVnfLcmV1(base.FixturedTestCase):
client_fixture_class = client.ClientFixture client_fixture_class = client.ClientFixture
api_version = '1' api_version = '1'

View File

@ -13,8 +13,18 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from io import StringIO
import os
import sys
from unittest import mock
from tackerclient.common import exceptions
from tackerclient.osc.v1.vnflcm import vnflcm
from tackerclient.tests.unit.osc import base from tackerclient.tests.unit.osc import base
from tackerclient.tests.unit.osc.v1.fixture_data import client from tackerclient.tests.unit.osc.v1.fixture_data import client
from tackerclient.tests.unit.osc.v1 import test_vnflcm
from tackerclient.tests.unit.osc.v1 import vnflcm_fakes
from tackerclient.v1_0 import client as proxy_client
class TestVnfLcmV2(base.FixturedTestCase): class TestVnfLcmV2(base.FixturedTestCase):
@ -30,3 +40,112 @@ class TestVnfLcmV2(base.FixturedTestCase):
self.assertEqual(self.cs.vnf_lcm_client.vnf_instances_path, self.assertEqual(self.cs.vnf_lcm_client.vnf_instances_path,
'/vnflcm/v2/vnf_instances') '/vnflcm/v2/vnf_instances')
# check of other paths is omitted. # check of other paths is omitted.
class TestChangeVnfPkgVnfLcm(test_vnflcm.TestVnfLcm):
api_version = '2'
def setUp(self):
super(TestChangeVnfPkgVnfLcm, self).setUp()
self.change_vnfpkg_vnf_lcm = vnflcm.ChangeVnfPkgVnfLcm(
self.app, self.app_args,
cmd_name='vnflcm change-vnfpkg')
def test_take_action(self):
vnf_instance = vnflcm_fakes.vnf_instance_response()
sample_param_file = ("./tackerclient/osc/v2/vnflcm/samples/"
"change_vnfpkg_vnf_instance_param_sample.json")
arglist = [vnf_instance['id'], sample_param_file]
verifylist = [('vnf_instance', vnf_instance['id']),
('request_file', sample_param_file)]
# command param
parsed_args = self.check_parser(self.change_vnfpkg_vnf_lcm,
arglist,
verifylist)
url = os.path.join(self.url, 'vnflcm/v2/vnf_instances',
vnf_instance['id'], 'change_vnfpkg')
self.requests_mock.register_uri(
'POST', url, headers=self.header, json={})
sys.stdout = buffer = StringIO()
with mock.patch.object(proxy_client.ClientBase,
'_handle_fault_response') as m:
self.change_vnfpkg_vnf_lcm.take_action(parsed_args)
# check no fault response is received
self.assertNotCalled(m)
self.assertEqual(
('Change Current VNF Package for VNF Instance {0} '
'has been accepted.'.format(vnf_instance['id'])),
buffer.getvalue().strip())
def test_take_action_vnf_instance_not_found(self):
vnf_instance = vnflcm_fakes.vnf_instance_response()
sample_param_file = ("./tackerclient/osc/v1/vnflcm/samples/"
"change_vnfpkg_vnf_instance_param_sample.json")
arglist = [vnf_instance['id'], sample_param_file]
verifylist = [('vnf_instance', vnf_instance['id']),
('request_file', sample_param_file)]
# command param
parsed_args = self.check_parser(self.change_vnfpkg_vnf_lcm,
arglist,
verifylist)
url = os.path.join(self.url, 'vnflcm/v2/vnf_instances',
vnf_instance['id'], 'change_vnfpkg')
self.requests_mock.register_uri(
'POST', url, headers=self.header, status_code=404, json={})
self.assertRaises(exceptions.TackerClientException,
self.change_vnfpkg_vnf_lcm.take_action,
parsed_args)
def test_take_action_param_file_not_exists(self):
vnf_instance = vnflcm_fakes.vnf_instance_response()
sample_param_file = "./not_exists.json"
arglist = [vnf_instance['id'], sample_param_file]
verifylist = [('vnf_instance', vnf_instance['id']),
('request_file', sample_param_file)]
# command param
parsed_args = self.check_parser(
self.change_vnfpkg_vnf_lcm,
arglist,
verifylist)
ex = self.assertRaises(
exceptions.InvalidInput,
self.change_vnfpkg_vnf_lcm.take_action,
parsed_args)
expected_msg = ("Invalid input: File %s does not exist "
"or user does not have read privileges to it")
self.assertEqual(expected_msg % sample_param_file, str(ex))
@mock.patch("os.open")
@mock.patch("os.access")
def test_take_action_invalid_format_param_file(self, mock_access,
mock_open):
vnf_instance = vnflcm_fakes.vnf_instance_response()
sample_param_file = "./invalid_param_file.json"
arglist = [vnf_instance['id'], sample_param_file]
verifylist = [('vnf_instance', vnf_instance['id']),
('request_file', sample_param_file)]
mock_open.return_value = "invalid_json_data"
mock_access.return_value = True
# command param
parsed_args = self.check_parser(self.change_vnfpkg_vnf_lcm,
arglist,
verifylist)
ex = self.assertRaises(
exceptions.InvalidInput,
self.change_vnfpkg_vnf_lcm.take_action,
parsed_args)
expected_msg = "Failed to load parameter file."
self.assertIn(expected_msg, str(ex))

View File

@ -966,6 +966,15 @@ class VnfLCMClient(ClientBase):
return self.post((self.vnf_instance_path + "/change_ext_conn") % return self.post((self.vnf_instance_path + "/change_ext_conn") %
vnf_id, body=body, headers=self.headers) vnf_id, body=body, headers=self.headers)
@APIParamsCall
def change_vnfpkg_vnf_instance(self, vnf_id, body):
# NOTE: it is only supported by V2-API.
if self.vnf_instance_path.split('/')[2] == 'v2':
return self.post((self.vnf_instance_path + "/change_vnfpkg") %
vnf_id, body=body, headers=self.headers)
else:
raise exceptions.UnsupportedCommandVersion(version='1')
@APIParamsCall @APIParamsCall
def retry_vnf_instance(self, occ_id): def retry_vnf_instance(self, occ_id):
return self.post((self.vnf_lcm_op_occs_path + "/retry") % occ_id, return self.post((self.vnf_lcm_op_occs_path + "/retry") % occ_id,
@ -1272,6 +1281,9 @@ class Client(object):
def change_ext_conn_vnf_instance(self, vnf_id, body): def change_ext_conn_vnf_instance(self, vnf_id, body):
return self.vnf_lcm_client.change_ext_conn_vnf_instance(vnf_id, body) return self.vnf_lcm_client.change_ext_conn_vnf_instance(vnf_id, body)
def change_vnfpkg_vnf_instance(self, vnf_id, body):
return self.vnf_lcm_client.change_vnfpkg_vnf_instance(vnf_id, body)
def delete_vnf_instance(self, vnf_id): def delete_vnf_instance(self, vnf_id):
return self.vnf_lcm_client.delete_vnf_instance(vnf_id) return self.vnf_lcm_client.delete_vnf_instance(vnf_id)