Support Flow of the Get Operation Status

Supported Flow of the Get Operation Status as the part of
LCM notifications for VNF based on ETSI NFV-SOL specification.

Implements: blueprint support-etsi-nfv-specs
Spec: https://specs.openstack.org/openstack/tacker-specs/specs/victoria/support-notification-api-based-on-etsi-nfv-sol.html

Change-Id: Id23d025aeba568c4c67adc745171c0540140d233
This commit is contained in:
Koichi Edagawa 2020-08-31 16:43:31 +09:00 committed by Aldinson C. Esto
parent f8f58e8cec
commit 13065dfbdd
11 changed files with 789 additions and 3 deletions

View File

@ -12,6 +12,12 @@ vnf_instance_id:
in: path
required: true
type: string
vnf_lcm_op_occ_id:
description: |
Identifier of the VNF lifecycle management operation occurrence.
in: path
required: true
type: string
# variables in body
added_storage_resource_ids:
@ -714,6 +720,15 @@ is_automatic_invocation:
in: body
required: true
type: boolean
is_cancel_pending:
description: |
If the VNF LCM operation occurrence is in "STARTING",
"PROCESSING" or "ROLLING_BACK" state and the
operation is being cancelled, this attribute shall be set to
true. Otherwise, it shall be set to false.
in: body
required: true
type: boolean
is_dynamic:
description: |
Indicates whether this set of addresses was assigned dynamically (true)
@ -827,6 +842,45 @@ notification_vnf_lcm_op_occ_id:
in: body
required: true
type: string
operation:
description: |
Type of the actual LCM operation represented by this
VNF LCM operation occurrence.
in: body
required: true
type: string
operation_params:
description: |
Input parameters of the LCM operation. This attribute
shall be formatted according to the request data type of
the related LCM operation.
The following mapping between operationType and the
data type of this attribute shall apply:
INSTANTIATE: InstantiateVnfRequest
SCALE: ScaleVnfRequest
HEAL: HealVnfRequest
TERMINATE: TerminateVnfRequest
MODIFY_INFO: VnfInfoModificationRequest
This attribute shall be present if this data type is returned
in a response to reading an individual resource, and may
be present according to the chosen attribute selector
parameter if this data type is returned in a response to a
query of a container resource.
in: body
required: false
type: string
operation_state:
description: |
The state of the LCM operation.
in: body
required: true
type: string
removed_storage_resource_ids:
description: |
References to VirtualStorage resources that
@ -840,6 +894,14 @@ removed_storage_resource_ids:
in: body
required: false
type: array
resource_changes:
description: |
This attribute contains information about the cumulative
changes to virtualised resources that were performed so
far by the LCM operation since its start, if applicable.
in: body
required: false
type: object
resource_handle:
description: |
Reference to the resource realizing this VL.
@ -884,6 +946,18 @@ scale_status_scale_level:
in: body
required: true
type: string
start_time:
description: |
Date-time of the start of the operation.
in: body
required: true
type: string
state_entered_time:
description: |
Date-time when the current state has been entered.
in: body
required: true
type: string
subnet_id:
description: |
Subnet defined by the identifier of the subnet resource in the VIM.
@ -1170,6 +1244,18 @@ vnf_instance_vnfd_version:
in: body
required: true
type: string
vnf_lcm_op_occ_id_response:
description: |
Identifier of this VNF lifecycle management operation occurrence.
in: body
required: true
type: string
vnf_lcm_vnf_instance_id:
description: |
Identifier of the VNF instance to which the operation applies.
in: body
required: true
type: string
vnf_link_port_cp_instance_id:
description: |
When the link port is used for external connectivity by the VNF, this

View File

@ -0,0 +1,70 @@
{
"id": "d85c6ae4-af16-42c0-96fc-82f7c014c468",
"operationState": "COMPLETED",
"stateEnteredTime": "2020-08-02T06:50:50.883373",
"startTime": "2020-08-02T06:41:34.883483",
"vnfInstanceId": "0b7b95a9-21d5-4ac4-80c8-9ae9f7323787",
"operation": "INSTANTIATE",
"isAutomaticInvocation": false,
"operationParams": "{
"flavourId": "default",
"instantiationLevelId": "vnf-min",
}"
"isCancelPending": false,
"resourceChanges": {
"affectedVnfcs": [
{
"id": "36e24439-829c-4803-a413-385cd658d544",
"vduId": "VDU",
"changeType": "ADDED",
"computeResource": {
"vimConnectionId": "f26f181d-7891-4720-b022-b074ec1733ef",
"resourceId": "e0510ba9-3a53-4fcf-9dcc-58dea5c048b0",
"vimLevelResourceType": "OS::Nova::Server",
},
"affectedVnfcCpIds": [
"VDU1_CP0",
"VDU1_CP1"
],
"addedStorageResourceIds": [
"81ae44f6-b65b-47aa-a578-e53b7a50a574"
]
}
],
"affectedVirtualLinks": [
{
"id": "9836f7f2-5af4-4df5-a89f-933479448ef7",
"vnfVirtualLinkDescId": "internalNW",
"changeType": "ADDED",
"networkResource": {
"vimConnectionId": "f26f181d-7891-4720-b022-b074ec1733ef",
"resourceId": "400692e5-b2db-478e-acb1-b77a92635ec6",
"vimLevelResourceType": "OS::Neutron::Net"
}
}
],
"affectedVirtualStorages": [
{
"id": "81ae44f6-b65b-47aa-a578-e53b7a50a574",
"virtualStorageDescId": "Storage",
"changeType": "ADDED",
"storageResource": {
"vimConnectionId": "f26f181d-7891-4720-b022-b074ec1733ef",
"resourceId": "842f527e-0092-4f11-aede-f981ba4fd884",
"vimLevelResourceType": "OS::Cinder::Volume"
}
}
]
},
"_links": {
"self": {
"href": "http://sample.com/vnflcm/v1/vnf_lcm_op_occs/d85c6ae4-af16-42c0-96fc-82f7c014c468"
},
"vnfInstance": {
"href": "http://sample.com/vnflcm/v1/vnf_instances/0b7b95a9-21d5-4ac4-80c8-9ae9f7323787"
},
"grant": {
"href": "/grant/v1/grants/3432cebe-db0a-11e8-9023-005056317abe"
}
}
}

View File

@ -570,6 +570,110 @@ Response Example
.. literalinclude:: samples/vnflcm/list-vnf-instance-response.json
:language: javascript
Show VNF LCM operation occurrence
=================================
.. rest_method:: GET /vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId}
The client can use this method to retrieve status information about a VNF lifecycle management operation occurrence
by reading an "Individual VNF LCM operation occurrence" resource.
Response Codes
--------------
.. rest_status_code:: success status.yaml
- 200
.. rest_status_code:: error status.yaml
- 400
- 401
- 403
- 404
Request Parameters
------------------
.. rest_parameters:: parameters_vnflcm.yaml
- vnfLcmOpOccId: vnf_lcm_op_occ_id
Response Parameters
-------------------
.. rest_parameters:: parameters_vnflcm.yaml
- id: vnf_lcm_op_occ_id_response
- operationState: operation_state
- stateEnteredTime: state_entered_time
- startTime: start_time
- vnfInstanceId: vnf_lcm_vnf_instance_id
- operation: operation
- isAutomaticInvocation: is_automatic_invocation
- operationParams: operation_params
- isCancelPending: is_cancel_pending
- error: error
- title: error_title
- status: error_status
- detail: error_detail
- resourceChanges: resource_changes
- affectedVnfcs: affected_vnfcs
- id: affected_vnfcs_id
- vduId: affected_vnfcs_vdu_id
- changeType: affected_vnfcs_change_type
- computeResource: vnfc_resource_info_compute_resource
- vimConnectionId: vim_connection_id
- resourceId: resource_handle_resource_id
- vimLevelResourceType: resource_handle_vim_level_resource_type
- affectedVnfcCpIds: affected_vnfc_cp_ids
- addedStorageResourceIds: added_storage_resource_ids
- removedStorageResourceIds: removed_storage_resource_ids
- affectedVirtualLinks: affected_virtual_links
- id: affected_virtual_links_id
- vnfVirtualLinkDescId: vnf_virtual_link_resource_info_vnf_virtual_link_desc_id
- changeType: affected_virtual_links_change_type
- networkResource: vnf_virtual_link_resource_info_network_resource
- vimConnectionId: vim_connection_id
- resourceId: resource_handle_resource_id
- vimLevelResourceType: resource_handle_vim_level_resource_type
- affectedVirtualStorages: affected_virtual_storages
- id: affected_virtual_storages_id
- virtualStorageDescId: affected_virtual_storages_virtual_storage_desc_id
- changeType: affected_virtual_storages_change_type
- storageResource: virtual_storage_resource_info_storage_resource
- vimConnectionId: vim_connection_id
- resourceId: resource_handle_resource_id
- vimLevelResourceType: resource_handle_vim_level_resource_type
- changedInfo: changed_info
- vnfInstanceName: changed_info_vnf_instance_name
- vnfInstanceDescription: changed_info_vnf_instance_description
- metadata: changed_info_metadata
- vimConnectionInfo: changed_info_vim_connection_info
- id: vim_connection_info_id
- vimId: vim_connection_info_vim_id
- vimType: vim_connection_info_vim_type
- interfaceInfo: vim_connection_info_interface_info
- endpoint: vim_connection_info_interface_info_endpoint
- accessInfo: vim_connection_info_access_info
- username: vim_connection_info_access_info_username
- region: vim_connection_info_access_info_region
- password: vim_connection_info_access_info_password
- tenant: vim_connection_info_access_info_tenant
- vnfPkgId: changed_info_vnf_pkg_id
- vnfdId: changed_info_vnfd_id
- vnfProvider: changed_info_vnf_provider
- vnfProductName: changed_info_vnf_product_name
- vnfSotwareVersion: changed_info_vnf_sotware_version
- vnfdVersion: changed_info_vnfd_version
- _links: vnf_instance_links
Response Example
----------------
.. literalinclude:: samples/vnflcm/show-vnflcm-operation-occurrence-response.json
:language: javascript
Create a new subscription
=========================

View File

@ -89,7 +89,35 @@ class ViewBuilder(base.BaseViewBuilder):
return vim_connections
def _get_vnf_instance_info(self, vnf_instance):
def _get_lcm_op_occs_links(self, vnf_lcm_op_occs):
_links = {
"self": {
"href": '%(endpoint)s/vnflcm/v1/vnf_lcm_op_occs/%(id)s'
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.id}
},
"vnfInstance": {
"href": '%(endpoint)s/vnflcm/v1/vnf_instances/%(id)s'
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.vnf_instance_id}
},
"rollback": {
"href":
'%(endpoint)s/vnflcm/v1/vnf_lcm_op_occs/%(id)s/rollback'
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.id}
},
"grant": {
"href": '%(endpoint)s/vnflcm/v1/vnf_lcm_op_occs/%(id)s/grant'
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.id}
}
}
return {"_links": _links}
def _get_vnf_instance_info(self,
vnf_instance, api_version=None):
vnf_instance_dict = vnf_instance.to_dict()
if vnf_instance_dict.get('vim_connection_info'):
vnf_instance_dict['vim_connection_info'] = \
@ -108,6 +136,17 @@ class ViewBuilder(base.BaseViewBuilder):
vnf_instance_dict.update(links)
return vnf_instance_dict
def _get_vnf_lcm_op_occs(self, vnf_lcm_op_occs):
vnf_lcm_op_occs_dict = vnf_lcm_op_occs.to_dict()
vnf_lcm_op_occs_dict.pop('error_point')
vnf_lcm_op_occs_dict = utils.convert_snakecase_to_camelcase(
vnf_lcm_op_occs_dict)
links = self._get_lcm_op_occs_links(vnf_lcm_op_occs)
vnf_lcm_op_occs_dict.update(links)
return vnf_lcm_op_occs_dict
def create(self, vnf_instance):
return self._get_vnf_instance_info(vnf_instance)
@ -224,3 +263,6 @@ class ViewBuilder(base.BaseViewBuilder):
def subscription_show(self, vnf_lcm_subscriptions):
return self._get_vnf_lcm_subscription(vnf_lcm_subscriptions)
def show_lcm_op_occs(self, vnf_lcm_op_occs):
return self._get_vnf_lcm_op_occs(vnf_lcm_op_occs)

View File

@ -545,6 +545,24 @@ class VnfLcmController(wsgi.Controller):
vnf_instance = self._get_vnf_instance(context, id)
self._heal(context, vnf_instance, vnf, body)
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND))
def show_lcm_op_occs(self, request, id):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'show_lcm_op_occs')
try:
vnf_lcm_op_occs = objects.VnfLcmOpOcc.get_by_id(context, id)
except exceptions.NotFound as occ_e:
return self._make_problem_detail(str(occ_e),
404, title='VnfLcmOpOcc NOT FOUND')
except Exception as e:
LOG.error(traceback.format_exc())
return self._make_problem_detail(str(e),
500, title='Internal Server Error')
return self._view_builder.show_lcm_op_occs(vnf_lcm_op_occs)
@wsgi.response(http_client.CREATED)
@validation.schema(vnf_lcm.register_subscription)
def register_subscription(self, request, body):

View File

@ -77,6 +77,12 @@ class VnflcmAPIRouter(wsgi.Router):
"/vnf_instances/{id}/heal",
methods, controller, default_resource)
# Allowed methods on
# /vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId} resource
methods = {"GET": "show_lcm_op_occs"}
self._setup_route(mapper, "/vnf_lcm_op_occs/{id}",
methods, controller, default_resource)
# Allowed methods on
# /vnflcm/v1/vnf_instances/{vnfInstanceId}/terminate resource
methods = {"POST": "terminate"}

View File

@ -186,6 +186,37 @@ class VnfcState(BaseTackerEnum):
ALL = (STARTED, STOPPED)
class InstanceOperationalState(BaseTackerEnum):
STARTING = 'STARTING'
PROCESSING = 'PROCESSING'
COMPLETED = 'COMPLETED'
FAILED_TEMP = 'FAILED_TEMP'
ROLLING_BACK = 'ROLLING_BACK'
ROLLED_BACK = 'ROLLED_BACK'
ALL = (STARTING, PROCESSING, COMPLETED, FAILED_TEMP,
ROLLING_BACK, ROLLED_BACK)
class InstanceOperationalStateField(BaseEnumField):
AUTO_TYPE = InstanceOperationalState()
class InstanceOperation(BaseTackerEnum):
INSTANTIATE = 'INSTANTIATE'
SCALE = 'SCALE'
TERMINATE = 'TERMINATE'
HEAL = 'HEAL'
MODIFY_INFO = 'MODIFY_INFO'
ALL = (INSTANTIATE, SCALE,
TERMINATE, HEAL, MODIFY_INFO)
class InstanceOperationField(BaseEnumField):
AUTO_TYPE = InstanceOperation()
class LcmOccsOperationState(BaseTackerEnum):
STARTING = 'STARTING'
PROCESSING = 'PROCESSING'

View File

@ -77,6 +77,17 @@ rules = [
}
]
),
policy.DocumentedRuleDefault(
name=VNFLCM % 'show_lcm_op_occs',
check_str=base.RULE_ADMIN_OR_OWNER,
description="Query an Individual VNF LCM operation occurrence",
operations=[
{
'method': 'GET',
'path': '/vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId}'
}
]
),
policy.DocumentedRuleDefault(
name=VNFLCM % 'index',
check_str=base.RULE_ADMIN_OR_OWNER,

View File

@ -103,6 +103,7 @@ def get_vnf_package_vnfd():
def get_lcm_op_occs_data():
return {
"id": uuidsentinel.lcm_op_occs_id,
"tenant_id": uuidsentinel.tenant_id,
'operation_state': 'PROCESSING',
'state_entered_time':

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from copy import deepcopy
import datetime
import iso8601
import os
@ -32,6 +32,9 @@ from tacker.tests import constants
from tacker.tests import uuidsentinel
from tacker import wsgi
import tacker.conf
CONF = tacker.conf.CONF
def return_default_vim():
default_vim = {
@ -140,12 +143,44 @@ def return_vnf_instance_model(
def return_vnf_instance(
instantiated_state=fields.VnfInstanceState.NOT_INSTANTIATED,
scale_status=None,
**updates):
if instantiated_state == fields.VnfInstanceState.NOT_INSTANTIATED:
data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state
vnf_instance_obj = objects.VnfInstance(**data)
elif scale_status:
data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state
vnf_instance_obj = objects.VnfInstance(**data)
get_instantiated_vnf_info = {
'flavour_id': uuidsentinel.flavour_id,
'vnf_state': 'STARTED',
'instance_id': uuidsentinel.instance_id
}
instantiated_vnf_info = get_instantiated_vnf_info
s_status = {"aspect_id": "SP1", "scale_level": 1}
scale_status = objects.ScaleInfo(**s_status)
instantiated_vnf_info.update(
{"ext_cp_info": [],
'ext_virtual_link_info': [],
'ext_managed_virtual_link_info': [],
'vnfc_resource_info': [],
'vnf_virtual_link_resource_info': [],
'virtual_storage_resource_info': [],
"flavour_id": "simple",
"scale_status": [scale_status],
"vnf_instance_id": "171f3af2-a753-468a-b5a7-e3e048160a79",
"additional_params": {"key": "value"},
'vnf_state': "STARTED"})
info_data = objects.InstantiatedVnfInfo(**instantiated_vnf_info)
vnf_instance_obj.instantiated_vnf_info = info_data
else:
data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state
@ -704,6 +739,27 @@ def get_instantiate_vnf_request_with_ext_virtual_links(**updates):
return instantiate_vnf_request
def _get_vnf(**updates):
vnf_data = {
'tenant_id': uuidsentinel.tenant_id,
'name': "fake_name",
'vnfd_id': uuidsentinel.vnfd_id,
'vnf_instance_id': uuidsentinel.instance_id,
'mgmt_ip_address': "fake_mgmt_ip_address",
'status': 'ACTIVE',
'description': 'fake_description',
'placement_attr': 'fake_placement_attr',
'vim_id': 'uuidsentinel.vim_id',
'error_reason': 'fake_error_reason',
'attributes': {
"scale_group": '{"scaleGroupDict" : {"SP1": {"maxLevel" : 3}}}'}}
if updates:
vnf_data.update(**updates)
return vnf_data
def get_dummy_grant_response():
return {'VDU1': {'checksum': {'algorithm': 'fake algo',
'hash': 'fake hash'},
@ -756,3 +812,210 @@ def wsgi_app_v1(fake_auth_context=None):
uuidsentinel.project_id, is_admin=True)
api_v1 = InjectContext(ctxt, inner_app_v1)
return api_v1
VNFLCMOPOCC_RESPONSE = {
'_links': {
"self": {
"href": CONF.vnf_lcm.endpoint_url + '/vnflcm/v1/vnf_lcm_op_occs/'
'f26f181d-7891-4720-b022-b074ec1733ef'
},
"vnfInstance": {
"href": CONF.vnf_lcm.endpoint_url + '/vnflcm/v1/vnf_instances/'
'f26f181d-7891-4720-b022-b074ec1733ef'
},
"rollback": {
"href": CONF.vnf_lcm.endpoint_url + '/vnflcm/v1/vnf_lcm_op_occs/'
'f26f181d-7891-4720-b022-b074ec1733ef/rollback'
},
"grant": {
"href": CONF.vnf_lcm.endpoint_url + '/vnflcm/v1/vnf_lcm_op_occs/'
'f26f181d-7891-4720-b022-b074ec1733ef/grant'
}},
'operationState': 'COMPLETED',
'stateEnteredTime': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'startTime': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'vnfInstanceId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'operation': 'MODIFY_INFO',
'isAutomaticInvocation': False,
'operationParams': '{"is_reverse": False, "is_auto": False}',
'error': {
'status': 500,
'detail': "name 'con' is not defined",
'title': "ERROR"
},
'id': 'f26f181d-7891-4720-b022-b074ec1733ef',
'isCancelPending': False,
'resourceChanges': {
'affectedVnfcs': [{
'id': 'f26f181d-7891-4720-b022-b074ec1733ef',
'vduId': 'VDU1',
'changeType': 'ADDED',
'computeResource': {
'vimConnectionId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'resourceId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'vimLevelResourceType': "OS::Nova::Server",
},
'affectedVnfcCpIds': [],
'addedStorageResourceIds': [],
'removedStorageResourceIds': []
}],
'affectedVirtualLinks': [{
'id': 'f26f181d-7891-4720-b022-b074ec1733ef',
'vnfVirtualLinkDescId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'changeType': 'ADDED',
'networkResource': {
'vimConnectionId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'resourceId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'vimLevelResourceType': 'COMPUTE'
}
}],
'affectedVirtualStorages': [{
'id': 'f26f181d-7891-4720-b022-b074ec1733ef',
'virtualStorageDescId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'changeType': 'ADDED',
'storageResource': {
'vimConnectionId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'resourceId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'vimLevelResourceType': 'COMPUTE'
}
}]
},
'changedInfo': {
'vimConnectionInfo': [],
'vimConnectionInfoDeleteIds': [],
'vnfPkgId': None,
'vnfInstanceName': 'fake_name',
'vnfInstanceDescription': "fake_vnf_instance_description",
'vnfdId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'vnfProvider': 'fake_vnf_provider',
'vnfProductName': 'fake_vnf_product_name',
'vnfSoftwareVersion': 'fake_vnf_software_version',
'vnfdVersion': 'fake_vnfd_version'
}
}
VNFLCMOPOCC_INDEX_RESPONSE = [VNFLCMOPOCC_RESPONSE]
def index_response(remove_attrs=None, vnf_lcm_op_occs_updates=None):
# Returns VNFLCMOPOCC_RESPONSE
# parameter remove_attrs is a list of attribute names
# to be removed before returning the response
if not remove_attrs:
return VNFLCMOPOCC_INDEX_RESPONSE
vnf_lcm_op_occs = deepcopy(VNFLCMOPOCC_RESPONSE)
for attr in remove_attrs:
vnf_lcm_op_occs.pop(attr, None)
if vnf_lcm_op_occs_updates:
vnf_lcm_op_occs.update(vnf_lcm_op_occs_updates)
return [vnf_lcm_op_occs]
def fake_vnf_lcm_op_occs():
error = {"status": 500, "detail": "name 'con' is not defined",
"title": "ERROR"}
error_obj = objects.ProblemDetails(**error)
compute_resource = {
"vim_connection_id":
"f26f181d-7891-4720-b022-b074ec1733ef",
"resource_id":
"f26f181d-7891-4720-b022-b074ec1733ef",
"vim_level_resource_type": "OS::Nova::Server"}
compute_resource_obj = objects.ResourceHandle(**compute_resource)
affected_vnfcs = {
"id": "f26f181d-7891-4720-b022-b074ec1733ef",
"vdu_id": "VDU1",
"change_type": "ADDED",
"compute_resource": compute_resource_obj,
"affected_vnfc_cp_ids": [],
"added_storage_resource_ids": [],
"removed_storage_sesource_ids": []
}
affected_vnfcs_obj = objects.AffectedVnfc(**affected_vnfcs)
network_resource = {
"vim_connection_id":
"f26f181d-7891-4720-b022-b074ec1733ef",
"resource_id":
"f26f181d-7891-4720-b022-b074ec1733ef",
"vim_level_resource_type": "COMPUTE"
}
network_resource_obj = \
objects.ResourceHandle(**network_resource)
affected_virtual_links = {
"id": "f26f181d-7891-4720-b022-b074ec1733ef",
"vnf_virtual_link_desc_id":
"f26f181d-7891-4720-b022-b074ec1733ef",
"change_type": "ADDED",
"network_resource": network_resource_obj,
}
affected_virtual_links_obj = \
objects.AffectedVirtualLink(**affected_virtual_links)
storage_resource = {
"vim_connection_id":
"f26f181d-7891-4720-b022-b074ec1733ef",
"resource_id":
"f26f181d-7891-4720-b022-b074ec1733ef",
"vim_level_resource_type": "COMPUTE"}
storage_resource_obj = \
objects.ResourceHandle(**storage_resource)
affected_virtual_storages = {
"id": "f26f181d-7891-4720-b022-b074ec1733ef",
"virtual_storage_desc_id":
"f26f181d-7891-4720-b022-b074ec1733ef",
"change_type": "ADDED",
"storage_resource": storage_resource_obj,
}
affected_virtual_storages_obj = \
objects.AffectedVirtualStorage(**affected_virtual_storages)
resource_changes = {
"affected_vnfcs": [affected_vnfcs_obj],
"affected_virtual_links": [affected_virtual_links_obj],
"affected_virtual_storages": [affected_virtual_storages_obj]
}
resource_changes_obj = objects.ResourceChanges(**resource_changes)
changed_info = {
"vnf_instance_name": "fake_name",
"vnf_instance_description":
"fake_vnf_instance_description",
"metadata": {},
"vnfd_id": "f26f181d-7891-4720-b022-b074ec1733ef",
"vnf_provider": "fake_vnf_provider",
"vnf_product_name": "fake_vnf_product_name",
"vnf_software_version": "fake_vnf_software_version",
"vnfd_version": "fake_vnfd_version"
}
changed_info_obj = objects.VnfInfoModifications(**changed_info)
vnf_lcm_op_occs = {
'id': constants.UUID,
'operation_state': 'COMPLETED',
'state_entered_time': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'start_time': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'vnf_instance_id': constants.UUID,
'operation': 'MODIFY_INFO',
'is_automatic_invocation': False,
'operation_params': '{"is_reverse": False, "is_auto": False}',
'is_cancel_pending': False,
'error': error_obj,
'resource_changes': resource_changes_obj,
'changed_info': changed_info_obj
}
return vnf_lcm_op_occs
def return_vnf_lcm_opoccs_obj():
vnf_lcm_op_occs = fake_vnf_lcm_op_occs()
obj = objects.VnfLcmOpOcc(**vnf_lcm_op_occs)
return obj

View File

@ -14,11 +14,15 @@
# under the License.
from unittest import mock
import ddt
import codecs
import os
import ddt
import json
from oslo_serialization import jsonutils
from six.moves import http_client
import urllib
import webob
from webob import exc
from tacker.api.vnflcm.v1 import controller
@ -27,6 +31,7 @@ from tacker.conductor.conductorrpc.vnf_lcm_rpc import VNFLcmRPCAPI
from tacker import context
import tacker.db.vnfm.vnfm_db
from tacker.extensions import nfvo
from tacker.extensions import vnfm
from tacker.manager import TackerManager
from tacker import objects
from tacker.objects import fields
@ -40,6 +45,13 @@ from tacker.tests import uuidsentinel
from tacker.vnfm import vim_client
def _get_template(name):
filename = os.path.abspath(os.path.join(os.path.dirname(__file__),
'../../etc/samples/' + str(name)))
f = codecs.open(filename, encoding='utf-8', errors='strict')
return f.read()
class FakeVNFMPlugin(mock.Mock):
def __init__(self):
@ -59,6 +71,100 @@ class FakeVNFMPlugin(mock.Mock):
self.cp32_id = '3d1bd2a2-bf0e-44d1-87af-a2c6b2cad3ed'
self.cp32_update_id = '064c0d99-5a61-4711-9597-2a44dc5da14b'
def get_vnfd(self, *args, **kwargs):
if 'VNF1' in args:
return {'id': self.vnf1_vnfd_id,
'name': 'VNF1',
'attributes': {'vnfd': _get_template(
'test-nsd-vnfd1.yaml')}}
elif 'VNF2' in args:
return {'id': self.vnf3_vnfd_id,
'name': 'VNF2',
'attributes': {'vnfd': _get_template(
'test-nsd-vnfd2.yaml')}}
def get_vnfds(self, *args, **kwargs):
if {'name': ['VNF1']} in args:
return [{'id': self.vnf1_vnfd_id}]
elif {'name': ['VNF3']} in args:
return [{'id': self.vnf3_vnfd_id}]
else:
return []
def get_vnfs(self, *args, **kwargs):
if {'vnfd_id': [self.vnf1_vnfd_id]} in args:
return [{'id': self.vnf1_vnf_id}]
elif {'vnfd_id': [self.vnf3_vnfd_id]} in args:
return [{'id': self.vnf3_vnf_id}]
else:
return None
def get_vnf(self, *args, **kwargs):
if self.vnf1_vnf_id in args:
return self.get_dummy_vnf_error()
elif self.vnf3_vnf_id in args:
return self.get_dummy_vnf_not_error()
else:
return self.get_dummy_vnf_active()
def get_vnf_resources(self, *args, **kwargs):
if self.vnf1_vnf_id in args:
return self.get_dummy_vnf1_details()
elif self.vnf1_update_vnf_id in args:
return self.get_dummy_vnf1_update_details()
elif self.vnf3_vnf_id in args:
return self.get_dummy_vnf3_details()
elif self.vnf3_update_vnf_id in args:
return self.get_dummy_vnf3_update_details()
def get_dummy_vnf1_details(self):
return [{'name': 'CP11', 'id': self.cp11_id},
{'name': 'CP12', 'id': self.cp12_id}]
def get_dummy_vnf1_update_details(self):
return [{'name': 'CP11', 'id': self.cp11_update_id},
{'name': 'CP12', 'id': self.cp12_update_id}]
def get_dummy_vnf3_details(self):
return [{'name': 'CP32', 'id': self.cp32_id}]
def get_dummy_vnf3_update_details(self):
return [{'name': 'CP32', 'id': self.cp32_update_id}]
def get_dummy_vnf_active(self):
return {'tenant_id': uuidsentinel.tenant_id,
'name': "fake_name",
'vnfd_id': uuidsentinel.vnfd_id,
'vnf_instance_id': uuidsentinel.instance_id,
'mgmt_ip_address': "fake_mgmt_ip_address",
'status': 'ACTIVE',
'description': 'fake_description',
'placement_attr': 'fake_placement_attr',
'vim_id': 'uuidsentinel.vim_id',
'error_reason': 'fake_error_reason',
'attributes': {
"scale_group": '{"scaleGroupDict":' +
'{"SP1": {"maxLevel" : 3}}}'}}
def get_dummy_vnf_error(self):
return {'tenant_id': uuidsentinel.tenant_id,
'name': "fake_name",
'vnfd_id': uuidsentinel.vnfd_id,
'vnf_instance_id': uuidsentinel.instance_id,
'mgmt_ip_address': "fake_mgmt_ip_address",
'status': 'ERROR',
'description': 'fake_description',
'placement_attr': 'fake_placement_attr',
'vim_id': 'uuidsentinel.vim_id',
'error_reason': 'fake_error_reason',
'attributes': {
"scale_group": '{"scaleGroupDict":' +
'{"SP1": {"maxLevel" : 3}}}'}}
def get_dummy_vnf_not_error(self):
msg = _('VNF %(vnf_id)s could not be found')
raise vnfm.VNFNotFound(explanation=msg)
@ddt.ddt
class TestController(base.TestCase):
@ -104,6 +210,27 @@ class TestController(base.TestCase):
return vnf_dict
def _make_problem_detail(
self,
detail,
status,
title=None,
type=None,
instance=None):
res = webob.Response(content_type='application/problem+json')
problemDetails = {}
if type:
problemDetails['type'] = type
if title:
problemDetails['title'] = title
problemDetails['detail'] = detail
problemDetails['status'] = status
if instance:
problemDetails['instance'] = instance
res.text = json.dumps(problemDetails)
res.status_int = status
return res
@mock.patch.object(objects.VnfInstance, 'save')
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_package.VnfPackage, 'get_by_id')
@ -1631,3 +1758,30 @@ class TestController(base.TestCase):
'/vnflcm/v1/vnf_instances?' + query)
self.assertRaises(exceptions.ValidationError,
self.controller.index, req)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VnfLcmOpOcc, "get_by_id")
def test_show_lcm_op_occs(self, mock_get_by_id,
mock_get_service_plugins):
req = fake_request.HTTPRequest.blank(
'/vnf_lcm_op_occs/%s' % constants.UUID)
mock_get_by_id.return_value = fakes.return_vnf_lcm_opoccs_obj()
expected_result = fakes.VNFLCMOPOCC_RESPONSE
res_dict = self.controller.show_lcm_op_occs(req, constants.UUID)
self.assertEqual(expected_result, res_dict)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VnfLcmOpOcc, "get_by_id")
def test_show_lcm_op_occs_not_found(self, mock_get_by_id,
mock_get_service_plugins):
req = fake_request.HTTPRequest.blank(
'/vnfpkgm/v1/vnf_packages/%s' % constants.UUID)
mock_get_by_id.side_effect = exceptions.NotFound()
req.headers['Content-Type'] = 'application/json'
req.method = 'GET'
resp = req.get_response(self.app)
self.assertEqual(http_client.NOT_FOUND, resp.status_code)