Add VNF resource details to get vnf API

Adds physical id and type of VDUs, CPs and VLs of a VNF to
get vnf API.

APIImpact
Change-Id: I469b91c1a000e7a47ae1d4313ed83de26e1391b2
Partial-Bug: #1602112
This commit is contained in:
Janki Chhatbar
2016-07-12 09:55:54 +05:30
committed by Janki
parent c7d279df84
commit 2e766e122b
8 changed files with 144 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
---
features:
- Added API to fetch VNF components details.

View File

@@ -140,6 +140,15 @@ class FilePathMissing(exceptions.InvalidInput):
"tosca.artifacts.Deployment.Image.VM artifact type") "tosca.artifacts.Deployment.Image.VM artifact type")
class InfraDriverUnreachable(exceptions.ServiceUnavailable):
message = _("Could not retrieve VNF resource IDs and"
" types. Please check %(service)s status.")
class VNFInactive(exceptions.InvalidInput):
message = _("VNF %(vnf_id)s is not in Active state %(message)s")
def _validate_service_type_list(data, valid_values=None): def _validate_service_type_list(data, valid_values=None):
if not isinstance(data, list): if not isinstance(data, list):
msg = _("invalid data format for service list: '%s'") % data msg = _("invalid data format for service list: '%s'") % data
@@ -358,6 +367,33 @@ SUB_RESOURCE_ATTRIBUTE_MAP = {
} }
} }
} }
},
'resources': {
'parent': {
'collection_name': 'vnfs',
'member_name': 'vnf'
},
'members': {
'resource': {
'parameters': {
'name': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'type': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'id': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
}
}
}
} }
} }
@@ -464,6 +500,10 @@ class VNFMPluginBase(service_base.NFVPluginBase):
def get_vnf(self, context, vnf_id, fields=None): def get_vnf(self, context, vnf_id, fields=None):
pass pass
@abc.abstractmethod
def get_vnf_resources(self, context, vnf_id, fields=None, filters=None):
pass
@abc.abstractmethod @abc.abstractmethod
def create_vnf(self, context, vnf): def create_vnf(self, context, vnf):
pass pass

View File

@@ -53,6 +53,12 @@ class VnfTestCreate(base.BaseTackerTest):
if vim_id: if vim_id:
self.assertEqual(vim_id, vnf_instance['vnf']['vim_id']) self.assertEqual(vim_id, vnf_instance['vnf']['vim_id'])
# Get vnf details when vnf is in active state
vnf_details = self.client.list_vnf_resources(vnf_id)['resources'][0]
self.assertIn('name', vnf_details)
self.assertIn('id', vnf_details)
self.assertIn('type', vnf_details)
# Delete vnf_instance with vnf_id # Delete vnf_instance with vnf_id
try: try:
self.client.delete_vnf(vnf_id) self.client.delete_vnf(vnf_id)

View File

@@ -20,6 +20,7 @@ import os
import yaml import yaml
from tacker import context from tacker import context
from tacker.extensions import vnfm
from tacker.tests.unit import base from tacker.tests.unit import base
from tacker.tests.unit.db import utils from tacker.tests.unit.db import utils
from tacker.vnfm.infra_drivers.heat import heat from tacker.vnfm.infra_drivers.heat import heat
@@ -143,6 +144,28 @@ class TestDeviceHeat(base.TestCase):
'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123', 'description': 'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123', 'description':
u'OpenWRT with services'} u'OpenWRT with services'}
def _get_expected_active_vnf(self):
return {'status': 'ACTIVE',
'instance_id': None,
'name': u'test_openwrt',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'vnfd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e',
'vnfd': {
'service_types': [{
'service_type': u'vnfd',
'id': u'4a4c2d44-8a52-4895-9a75-9d1c76c3e738'}],
'description': u'OpenWRT with services',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'mgmt_driver': u'openwrt',
'infra_driver': u'heat',
'attributes': {u'vnfd': self.vnfd_openwrt},
'id': u'fb048660-dc1b-4f0f-bd89-b023666650ec',
'name': u'openwrt_services'},
'mgmt_url': '{"vdu1": "192.168.120.31"}',
'service_context': [],
'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123',
'description': u'OpenWRT with services'}
def test_create(self): def test_create(self):
vnf_obj = utils.get_dummy_device_obj() vnf_obj = utils.get_dummy_device_obj()
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738' expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
@@ -408,3 +431,12 @@ class TestDeviceHeat(base.TestCase):
files={'scaling.yaml': 'hot_scale_custom.yaml'}, files={'scaling.yaml': 'hot_scale_custom.yaml'},
is_monitor=False is_monitor=False
) )
def test_get_resource_info(self):
vnf_obj = self._get_expected_active_vnf()
print(vnf_obj)
self.assertRaises(vnfm.InfraDriverUnreachable,
self.heat_driver.get_resource_info,
plugin=None, context=self.context, vnf_info=vnf_obj,
auth_attr=utils.get_vim_auth_obj(),
region_name=None)

View File

@@ -32,6 +32,10 @@ class FakeDriverManager(mock.Mock):
def invoke(self, *args, **kwargs): def invoke(self, *args, **kwargs):
if 'create' in args: if 'create' in args:
return str(uuid.uuid4()) return str(uuid.uuid4())
if 'get_resource_info' in args:
return {'resources': {'name': 'dummy_vnf',
'type': 'dummy',
'id': str(uuid.uuid4())}}
class FakeVNFMonitor(mock.Mock): class FakeVNFMonitor(mock.Mock):
@@ -208,6 +212,22 @@ class TestVNFMPlugin(db_base.SqlTestCase):
res_state=mock.ANY, res_type=constants.RES_TYPE_VNF, res_state=mock.ANY, res_type=constants.RES_TYPE_VNF,
tstamp=mock.ANY, details=mock.ANY) tstamp=mock.ANY, details=mock.ANY)
def test_show_vnf_details_vnf_inactive(self):
self._insert_dummy_device_template()
vnf_obj = utils.get_dummy_vnf_obj()
result = self.vnfm_plugin.create_vnf(self.context, vnf_obj)
self.assertRaises(vnfm.VNFInactive, self.vnfm_plugin.get_vnf_resources,
self.context, result['id'])
def test_show_vnf_details_vnf_active(self):
self._insert_dummy_device_template()
active_vnf = self._insert_dummy_device()
resources = self.vnfm_plugin.get_vnf_resources(self.context,
active_vnf['id'])[0]
self.assertIn('name', resources)
self.assertIn('type', resources)
self.assertIn('id', resources)
def test_delete_vnf(self): def test_delete_vnf(self):
self._insert_dummy_device_template() self._insert_dummy_device_template()
dummy_device_obj = self._insert_dummy_device() dummy_device_obj = self._insert_dummy_device()

View File

@@ -629,3 +629,24 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
self._handle_vnf_scaling(context, policy_) self._handle_vnf_scaling(context, policy_)
return scale['scale'] return scale['scale']
def get_vnf_resources(self, context, vnf_id, fields=None, filters=None):
vnf_info = self.get_vnf(context, vnf_id)
infra_driver = vnf_info['vnfd']['infra_driver']
auth = self.get_vim(context, vnf_info)
if vnf_info['status'] == constants.ACTIVE:
vnf_details = self._vnf_manager.invoke(infra_driver,
'get_resource_info',
plugin=self,
context=context,
vnf_info=vnf_info,
auth_attr=auth)
resources = [{'name': name,
'type': info.get('type'),
'id': info.get('id')}
for name, info in vnf_details.items()]
return resources
# Raise exception when VNF.status != ACTIVE
else:
raise vnfm.VNFInactive(vnf_id=vnf_id,
message=_(' Cannot fetch details'))

View File

@@ -68,3 +68,9 @@ class DeviceAbstractDriver(extensions.PluginInterface):
@abc.abstractmethod @abc.abstractmethod
def delete_wait(self, plugin, context, vnf_id): def delete_wait(self, plugin, context, vnf_id):
pass pass
@abc.abstractmethod
def get_resource_info(self, plugin, context, vnf_info, auth_attr,
region_name=None):
'''Fetches optional details of a VNF'''
pass

View File

@@ -854,6 +854,21 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver,
return jsonutils.dumps(mgmt_ips) return jsonutils.dumps(mgmt_ips)
def get_resource_info(self, plugin, context, vnf_info, auth_attr,
region_name=None):
stack_id = vnf_info['instance_id']
heatclient_ = HeatClient(auth_attr, region_name)
try:
resources_ids = heatclient_.resource_get_list(stack_id)
details_dict = {resource.resource_name:
{"id": resource.physical_resource_id,
"type": resource.resource_type}
for resource in resources_ids}
return details_dict
# Raise exception when Heat API service is not available
except Exception:
raise vnfm.InfraDriverUnreachable(service="Heat API service")
class HeatClient(object): class HeatClient(object):
def __init__(self, auth_attr, region_name=None): def __init__(self, auth_attr, region_name=None):
@@ -861,6 +876,7 @@ class HeatClient(object):
self.heat = clients.OpenstackClients(auth_attr, region_name).heat self.heat = clients.OpenstackClients(auth_attr, region_name).heat
self.stacks = self.heat.stacks self.stacks = self.heat.stacks
self.resource_types = self.heat.resource_types self.resource_types = self.heat.resource_types
self.resources = self.heat.resources
def create(self, fields): def create(self, fields):
fields = fields.copy() fields = fields.copy()