OSC support to instantiate and show vnf

Added new commands ``openstack vnflcm instantiate`` and
``openstack vnflcm show``.

Blueprint: support-etsi-nfv-specs
Change-Id: I528e20be6ec01c61b5ca6d646972a9ae22f1c158
This commit is contained in:
Shubham Potale
2019-12-10 18:53:50 +05:30
committed by tpatil
parent 66efce28d4
commit eae2c896cd
6 changed files with 436 additions and 13 deletions

View File

@@ -84,6 +84,8 @@ openstack.tackerclient.v1 =
vnf_package_upload = tackerclient.osc.v1.vnfpkgm.vnf_package:UploadVnfPackage
vnf_package_delete = tackerclient.osc.v1.vnfpkgm.vnf_package:DeleteVnfPackage
vnflcm_create = tackerclient.osc.v1.vnflcm.vnflcm:CreateVnfLcm
vnflcm_show = tackerclient.osc.v1.vnflcm.vnflcm:ShowVnfLcm
vnflcm_instantiate = tackerclient.osc.v1.vnflcm.vnflcm:InstantiateVnfLcm
[build_releasenotes]
all_files = 1

View File

@@ -0,0 +1,79 @@
{
"flavourId":"simple",
"instantiationLevelId":"instantiation_level_1",
"extVirtualLinks":[
{
"id":"ext-vl-uuid-VL1",
"vimConnectionId":"vim-uuid",
"resourceProviderId":"resource-provider-id",
"resourceId":"neutron-network-uuid_VL1",
"extCps":[
{
"cpdId":"CP1",
"cpConfig":[
{
"cpInstanceId":"cp-instance-id",
"linkPortId":"link-port-uuid_CP1",
"cpProtocolData":[
{
"layerProtocol":"IP_OVER_ETHERNET",
"ipOverEthernet":{
"macAddress":"00:25:96:FF:FE:12:34:56",
"ipAddresses":[
{
"addressRange":{
"minAddress":"192.168.11.01",
"maxAddress":"192.168.21.201"
},
"subnetId":"neutron-subnet-uuid_CP1"
}
]
}
}
]
}
]
}
],
"extLinkPorts":[
{
"id":"link-port-uuid_CP1",
"resourceHandle":{
"vimConnectionId":"vim-uuid",
"resourceProviderId":"resource-provider-id",
"resourceId":"neutron-port-uuid_CP1",
"vimLevelResourceType":"LINKPORT"
}
}
]
}
],
"extManagedVirtualLinks":[
{
"id":"extMngVLnk-uuid_VL3",
"vnfVirtualLinkDescId":"VL3",
"vimConnectionId":"vim-uuid",
"resourceProviderId":"resource-provider-id",
"resourceId":"neutron-network-uuid_VL3"
}
],
"vimConnectionInfo":[
{
"id":"vim-uuid",
"vimId":"dummy-vimid",
"vimType":"ETSINFV.OPENSTACK_KEYSTONE.v_2",
"interfaceInfo":{
"key1":"value1",
"key2":"value2"
},
"accessInfo":{
"key1":"value1",
"key2":"value2"
},
"extra":{
"key1":"value1",
"key2":"value2"
}
}
]
}

View File

@@ -13,18 +13,29 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import logging
import os
from osc_lib.cli import format_columns
from osc_lib.command import command
from osc_lib import utils
from tackerclient.common import exceptions
from tackerclient.i18n import _
from tackerclient.osc import sdk_utils
LOG = logging.getLogger(__name__)
_mixed_case_fields = ('vnfInstanceName', 'vnfInstanceDescription', 'vnfdId',
'vnfProvider', 'vnfProductName', 'vnfSoftwareVersion',
'vnfdVersion', 'instantiationState')
'vnfdVersion', 'instantiationState',
'vimConnectionInfo', 'instantiatedVnfInfo')
_VNF_INSTANCE = 'vnf_instance'
def _get_columns(item):
def _get_columns(vnflcm_obj, action=None):
column_map = {
'id': 'ID',
'vnfInstanceName': 'VNF Instance Name',
@@ -35,9 +46,19 @@ def _get_columns(item):
'vnfSoftwareVersion': 'VNF Software Version',
'vnfdVersion': 'VNFD Version',
'instantiationState': 'Instantiation State',
'links': 'Links',
'_links': 'Links',
}
return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
if action == 'show':
if vnflcm_obj['instantiationState'] == 'INSTANTIATED':
column_map.update(
{'instantiatedVnfInfo': 'Instantiated Vnf Info'}
)
column_map.update(
{'vimConnectionInfo': 'VIM Connection Info',
'_links': 'Links'}
)
return sdk_utils.get_osc_show_columns_for_sdk_resource(vnflcm_obj,
column_map)
class CreateVnfLcm(command.ShowOne):
@@ -58,10 +79,19 @@ class CreateVnfLcm(command.ShowOne):
'--description',
metavar="<vnf-instance-description>",
help=_('Description of the VNF instance to be created.'))
parser.add_argument(
'--I',
metavar="<param-file>",
help=_("Instantiate VNF subsequently after it's creation. "
"Specify instantiate request parameters in a json file."))
return parser
def args2body(self, parsed_args):
def args2body(self, parsed_args, file_path=None):
body = {}
if file_path:
return instantiate_vnf_args2body(file_path)
body['vnfdId'] = parsed_args.vnfd_id
if parsed_args.description:
@@ -75,8 +105,84 @@ class CreateVnfLcm(command.ShowOne):
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
vnf = client.create_vnf_instance(self.args2body(parsed_args))
if parsed_args.I:
# Instantiate VNF instance.
result = client.instantiate_vnf_instance(
vnf['id'],
self.args2body(parsed_args, file_path=parsed_args.I))
if not result:
print((_('VNF Instance %(id)s is created and instantiation'
' request has been accepted.') % {'id': vnf['id']}))
display_columns, columns = _get_columns(vnf)
data = utils.get_item_properties(
sdk_utils.DictModel(vnf),
columns, mixed_case_fields=_mixed_case_fields)
return (display_columns, data)
class ShowVnfLcm(command.ShowOne):
_description = _("Display VNF instance details")
def get_parser(self, prog_name):
parser = super(ShowVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_("VNF instance ID to display"))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
obj = client.show_vnf_instance(parsed_args.vnf_instance)
display_columns, columns = _get_columns(obj, action='show')
data = utils.get_item_properties(
sdk_utils.DictModel(obj),
columns, mixed_case_fields=_mixed_case_fields,
formatters={'instantiatedVnfInfo': format_columns.DictColumn})
return (display_columns, data)
def instantiate_vnf_args2body(file_path):
if file_path is not None and os.access(file_path, os.R_OK) is False:
msg = _("File %s does not exist or user does not have read "
"privileges to it")
raise exceptions.InvalidInput(msg % file_path)
try:
with open(file_path) as f:
body = json.load(f)
except (IOError, ValueError) as ex:
msg = _("Failed to load parameter file. Error: %s")
raise exceptions.InvalidInput(msg % ex)
if not body:
raise exceptions.InvalidInput(_('The parameter file is empty'))
return body
class InstantiateVnfLcm(command.Command):
_description = _("Instantiate a VNF Instance")
def get_parser(self, prog_name):
parser = super(InstantiateVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_("VNF instance ID to instantiate"))
parser.add_argument(
'instantiation_request_file',
metavar="<param-file>",
help=_('Specify instantiate request parameters in a json file.'))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.instantiate_vnf_instance(
parsed_args.vnf_instance, instantiate_vnf_args2body(
parsed_args.instantiation_request_file))
if not result:
print((_('Instantiate request for VNF Instance %(id)s has been'
' accepted.') % {'id': parsed_args.vnf_instance}))

View File

@@ -14,15 +14,19 @@
# under the License.
import ddt
from io import StringIO
import mock
import os
import sys
from oslo_utils.fixture import uuidsentinel
from tackerclient.common import exceptions
from tackerclient.osc.v1.vnflcm import vnflcm
from tackerclient.tests.unit.osc import base
from tackerclient.tests.unit.osc.v1.fixture_data import client
from tackerclient.tests.unit.osc.v1 import vnflcm_fakes
from tackerclient.v1_0 import client as proxy_client
class TestVnfLcm(base.FixturedTestCase):
@@ -38,10 +42,12 @@ class TestVnfLcm(base.FixturedTestCase):
self.app.client_manager.tackerclient = self.client_manager
def _get_columns_vnflcm():
def _get_columns_vnflcm(action='create'):
columns = ['ID', 'Instantiation State', 'VNF Instance Description',
'VNF Instance Name', 'VNF Product Name', 'VNF Provider',
'VNF Software Version', 'VNFD ID', 'VNFD Version', 'Links']
if action == 'show':
columns.extend(['Instantiated Vnf Info', 'VIM Connection Info'])
return columns
@@ -57,8 +63,10 @@ class TestCreateVnfLcm(TestVnfLcm):
self.assertRaises(base.ParserException, self.check_parser,
self.create_vnf_lcm, [], [])
@ddt.data(True, False)
def test_take_action(self, optional_arguments):
@ddt.data({"optional_arguments": True, "instantiate": True},
{"optional_arguments": False, "instantiate": False})
@ddt.unpack
def test_take_action(self, optional_arguments, instantiate):
arglist = [uuidsentinel.vnf_package_vnfd_id]
verifylist = [('vnfd_id', uuidsentinel.vnf_package_vnfd_id)]
@@ -69,6 +77,13 @@ class TestCreateVnfLcm(TestVnfLcm):
('description', 'test')])
# command param
if instantiate:
param_file = ("./tackerclient/osc/v1/vnflcm/samples/"
"instantiate_vnf_instance_param_sample.json")
arglist.extend(['--I', param_file])
verifylist.append(('I', param_file))
parsed_args = self.check_parser(self.create_vnf_lcm, arglist,
verifylist)
@@ -77,8 +92,148 @@ class TestCreateVnfLcm(TestVnfLcm):
'POST', os.path.join(self.url, 'vnflcm/v1/vnf_instances'),
json=json, headers=self.header)
if instantiate:
self.requests_mock.register_uri(
'POST', os.path.join(self.url, 'vnflcm/v1/vnf_instances',
json['id'], 'instantiate'),
json={}, headers=self.header)
sys.stdout = buffer = StringIO()
columns, data = (self.create_vnf_lcm.take_action(parsed_args))
expected_message = (
'VNF Instance ' + json['id'] + ' is created and instantiation '
'request has been accepted.')
if instantiate:
self.assertEqual(expected_message, buffer.getvalue().strip())
self.assertItemsEqual(_get_columns_vnflcm(),
columns)
self.assertItemsEqual(vnflcm_fakes.get_vnflcm_data(json),
data)
class TestShowVnfLcm(TestVnfLcm):
def setUp(self):
super(TestShowVnfLcm, self).setUp()
self.show_vnf_lcm = vnflcm.ShowVnfLcm(
self.app, self.app_args, cmd_name='vnflcm show')
def test_take_action(self):
vnf_instance = vnflcm_fakes.vnf_instance_response(
instantiation_state='INSTANTIATED')
arglist = [vnf_instance['id']]
verifylist = [('vnf_instance', vnf_instance['id'])]
# command param
parsed_args = self.check_parser(self.show_vnf_lcm, arglist,
verifylist)
self.requests_mock.register_uri(
'GET', os.path.join(self.url, 'vnflcm/v1/vnf_instances',
vnf_instance['id']),
json=vnf_instance, headers=self.header)
columns, data = (self.show_vnf_lcm.take_action(parsed_args))
self.assertItemsEqual(_get_columns_vnflcm(action='show'),
columns)
class TestInstantiateVnfLcm(TestVnfLcm):
def setUp(self):
super(TestInstantiateVnfLcm, self).setUp()
self.instantiate_vnf_lcm = vnflcm.InstantiateVnfLcm(
self.app, self.app_args, cmd_name='vnflcm instantiate')
def test_take_action(self):
vnf_instance = vnflcm_fakes.vnf_instance_response()
sample_param_file = ("./tackerclient/osc/v1/vnflcm/samples/"
"instantiate_vnf_instance_param_sample.json")
arglist = [vnf_instance['id'], sample_param_file]
verifylist = [('vnf_instance', vnf_instance['id']),
('instantiation_request_file', sample_param_file)]
# command param
parsed_args = self.check_parser(self.instantiate_vnf_lcm, arglist,
verifylist)
url = os.path.join(self.url, 'vnflcm/v1/vnf_instances',
vnf_instance['id'], 'instantiate')
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.instantiate_vnf_lcm.take_action(parsed_args)
# check no fault response is received
self.assertNotCalled(m)
self.assertEqual(
'Instantiate request for VNF Instance ' + vnf_instance['id'] +
' has been accepted.', 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/"
"instantiate_vnf_instance_param_sample.json")
arglist = [vnf_instance['id'], sample_param_file]
verifylist = [('vnf_instance', vnf_instance['id']),
('instantiation_request_file', sample_param_file)]
# command param
parsed_args = self.check_parser(self.instantiate_vnf_lcm, arglist,
verifylist)
url = os.path.join(self.url, 'vnflcm/v1/vnf_instances',
vnf_instance['id'], 'instantiate')
self.requests_mock.register_uri(
'POST', url, headers=self.header, status_code=404, json={})
self.assertRaises(exceptions.TackerClientException,
self.instantiate_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']),
('instantiation_request_file', sample_param_file)]
# command param
parsed_args = self.check_parser(self.instantiate_vnf_lcm, arglist,
verifylist)
ex = self.assertRaises(exceptions.InvalidInput,
self.instantiate_vnf_lcm.take_action,
parsed_args)
expected_msg = ("File %s does not exist or user does not have read "
"privileges to it")
self.assertEqual(expected_msg % sample_param_file, ex.message)
@mock.patch("os.open")
@mock.patch("os.access")
def test_take_action_invalid_format_param_file(self, mock_open,
mock_access):
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']),
('instantiation_request_file', sample_param_file)]
mock_open.return_value = "invalid_json_data"
# command param
parsed_args = self.check_parser(self.instantiate_vnf_lcm, arglist,
verifylist)
ex = self.assertRaises(exceptions.InvalidInput,
self.instantiate_vnf_lcm.take_action,
parsed_args)
expected_msg = "Failed to load parameter file."
self.assertIn(expected_msg, ex.message)

View File

@@ -16,7 +16,7 @@
from oslo_utils.fixture import uuidsentinel
def vnf_instance_response(attrs=None):
def vnf_instance_response(attrs=None, instantiation_state='NOT_INSTANTIATED'):
"""Create a fake vnf instance.
:param Dictionary attrs:
@@ -36,10 +36,74 @@ def vnf_instance_response(attrs=None):
"vnfProductName": "Sample VNF",
"vnfSoftwareVersion": "1.0",
"vnfdVersion": "1.0",
"instantiationState": "NOT_INSTANTIATED",
"links": "vnflcm/v1/vnf_instances/" + uuidsentinel.vnf_instance_id +
"/instantiate"
}
"_links": "vnflcm/v1/vnf_instances/" + uuidsentinel.vnf_instance_id +
"/instantiate",
"instantiationState": instantiation_state}
if instantiation_state == 'INSTANTIATED':
dummy_vnf_instance.update({
"vimConnectionInfo": [{
'id': uuidsentinel.uuid,
'vimId': uuidsentinel.vimId,
'vimType': 'openstack',
'interfaceInfo': {'k': 'v'},
'accessInfo': {'k': 'v'},
'extra': {'k': 'v'}
}],
"instantiatedVnfInfo": {
"flavourId": uuidsentinel.flavourId,
"vnfState": "STARTED",
"extCpInfo": [{
'id': uuidsentinel.extCpInfo_uuid,
'cpdId': uuidsentinel.cpdId_uuid,
'cpProtocolInfo': [{
'layerProtocol': 'IP_OVER_ETHERNET',
'ipOverEthernet': '{}'
}],
'extLinkPortId': uuidsentinel.extLinkPortId_uuid,
'metadata': {'k': 'v'},
'associatedVnfcCpId': uuidsentinel.associatedVnfcCpId_uuid
}],
"extVirtualLinkInfo": [{
'id': uuidsentinel.extVirtualLinkInfo_uuid,
'resourceHandle': {},
'extLinkPorts': []
}],
"extManagedVirtualLinkInfo": [{
"id": uuidsentinel.extManagedVirtualLinkInfo_uuid,
'vnfVirtualLinkDescId': {},
'networkResource': {},
'vnfLinkPorts': []
}],
"vnfcResourceInfo": [{
'id': uuidsentinel.vnfcResourceInfo_uuid,
'vduId': uuidsentinel.vduId_uuid,
'computeResource': {},
'storageResourceIds': [],
'reservationId': uuidsentinel.reservationId,
}],
"vnfVirtualLinkResourceInfo": [{
'id': uuidsentinel.vnfVirtualLinkResourceInfo,
'vnfVirtualLinkDescId': 'VL4',
'networkResource': {},
'reservationId': uuidsentinel.reservationId,
'vnfLinkPorts': [],
'metadata': {'k': 'v'}
}],
"virtualStorageResourceInfo": [{
'id': uuidsentinel.virtualStorageResourceInfo,
'virtualStorageDescId': uuidsentinel.virtualStorageDescId,
'storageResource': {},
'reservationId': uuidsentinel.reservationId,
'metadata': {'k': 'v'}
}]
},
"_links": {
'self': 'self_link',
'indicators': None,
'instantiate': 'instantiate_link'
}
})
return dummy_vnf_instance

View File

@@ -782,6 +782,7 @@ class VnfLCMClient(ClientBase):
"""
vnf_instances_path = '/vnflcm/v1/vnf_instances'
vnf_instance_path = '/vnflcm/v1/vnf_instances/%s'
def build_action(self, action):
return action
@@ -790,6 +791,15 @@ class VnfLCMClient(ClientBase):
def create_vnf_instance(self, body):
return self.post(self.vnf_instances_path, body=body)
@APIParamsCall
def show_vnf_instance(self, vnf_id, **_params):
return self.get(self.vnf_instance_path % vnf_id, params=_params)
@APIParamsCall
def instantiate_vnf_instance(self, vnf_id, body):
return self.post((self.vnf_instance_path + "/instantiate") % vnf_id,
body=body)
class Client(object):
"""Unified interface to interact with multiple applications of tacker service.
@@ -1041,3 +1051,10 @@ class Client(object):
def create_vnf_instance(self, body):
return self.vnf_lcm_client.create_vnf_instance(body)
def show_vnf_instance(self, vnf_instance, **_params):
return self.vnf_lcm_client.show_vnf_instance(vnf_instance,
**_params)
def instantiate_vnf_instance(self, vnf_id, body):
return self.vnf_lcm_client.instantiate_vnf_instance(vnf_id, body)