Merge "Support Flow of the Get Operation Status"

This commit is contained in:
Zuul 2020-10-05 15:53:31 +00:00 committed by Gerrit Code Review
commit 7a1f8651fa
11 changed files with 789 additions and 3 deletions

View File

@ -12,6 +12,12 @@ vnf_instance_id:
in: path in: path
required: true required: true
type: string 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 # variables in body
added_storage_resource_ids: added_storage_resource_ids:
@ -714,6 +720,15 @@ is_automatic_invocation:
in: body in: body
required: true required: true
type: boolean 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: is_dynamic:
description: | description: |
Indicates whether this set of addresses was assigned dynamically (true) Indicates whether this set of addresses was assigned dynamically (true)
@ -827,6 +842,45 @@ notification_vnf_lcm_op_occ_id:
in: body in: body
required: true required: true
type: string 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: removed_storage_resource_ids:
description: | description: |
References to VirtualStorage resources that References to VirtualStorage resources that
@ -840,6 +894,14 @@ removed_storage_resource_ids:
in: body in: body
required: false required: false
type: array 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: resource_handle:
description: | description: |
Reference to the resource realizing this VL. Reference to the resource realizing this VL.
@ -884,6 +946,18 @@ scale_status_scale_level:
in: body in: body
required: true required: true
type: string 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: subnet_id:
description: | description: |
Subnet defined by the identifier of the subnet resource in the VIM. Subnet defined by the identifier of the subnet resource in the VIM.
@ -1170,6 +1244,18 @@ vnf_instance_vnfd_version:
in: body in: body
required: true required: true
type: string 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: vnf_link_port_cp_instance_id:
description: | description: |
When the link port is used for external connectivity by the VNF, this 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 .. literalinclude:: samples/vnflcm/list-vnf-instance-response.json
:language: javascript :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 Create a new subscription
========================= =========================

View File

@ -89,7 +89,35 @@ class ViewBuilder(base.BaseViewBuilder):
return vim_connections 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() vnf_instance_dict = vnf_instance.to_dict()
if vnf_instance_dict.get('vim_connection_info'): if vnf_instance_dict.get('vim_connection_info'):
vnf_instance_dict['vim_connection_info'] = \ vnf_instance_dict['vim_connection_info'] = \
@ -108,6 +136,17 @@ class ViewBuilder(base.BaseViewBuilder):
vnf_instance_dict.update(links) vnf_instance_dict.update(links)
return vnf_instance_dict 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): def create(self, vnf_instance):
return self._get_vnf_instance_info(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): def subscription_show(self, vnf_lcm_subscriptions):
return self._get_vnf_lcm_subscription(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) vnf_instance = self._get_vnf_instance(context, id)
self._heal(context, vnf_instance, vnf, body) 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) @wsgi.response(http_client.CREATED)
@validation.schema(vnf_lcm.register_subscription) @validation.schema(vnf_lcm.register_subscription)
def register_subscription(self, request, body): def register_subscription(self, request, body):

View File

@ -77,6 +77,12 @@ class VnflcmAPIRouter(wsgi.Router):
"/vnf_instances/{id}/heal", "/vnf_instances/{id}/heal",
methods, controller, default_resource) 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 # Allowed methods on
# /vnflcm/v1/vnf_instances/{vnfInstanceId}/terminate resource # /vnflcm/v1/vnf_instances/{vnfInstanceId}/terminate resource
methods = {"POST": "terminate"} methods = {"POST": "terminate"}

View File

@ -186,6 +186,37 @@ class VnfcState(BaseTackerEnum):
ALL = (STARTED, STOPPED) 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): class LcmOccsOperationState(BaseTackerEnum):
STARTING = 'STARTING' STARTING = 'STARTING'
PROCESSING = 'PROCESSING' 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( policy.DocumentedRuleDefault(
name=VNFLCM % 'index', name=VNFLCM % 'index',
check_str=base.RULE_ADMIN_OR_OWNER, check_str=base.RULE_ADMIN_OR_OWNER,

View File

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

View File

@ -13,7 +13,7 @@
# 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 copy import deepcopy
import datetime import datetime
import iso8601 import iso8601
import os import os
@ -32,6 +32,9 @@ from tacker.tests import constants
from tacker.tests import uuidsentinel from tacker.tests import uuidsentinel
from tacker import wsgi from tacker import wsgi
import tacker.conf
CONF = tacker.conf.CONF
def return_default_vim(): def return_default_vim():
default_vim = { default_vim = {
@ -140,12 +143,44 @@ def return_vnf_instance_model(
def return_vnf_instance( def return_vnf_instance(
instantiated_state=fields.VnfInstanceState.NOT_INSTANTIATED, instantiated_state=fields.VnfInstanceState.NOT_INSTANTIATED,
scale_status=None,
**updates): **updates):
if instantiated_state == fields.VnfInstanceState.NOT_INSTANTIATED: if instantiated_state == fields.VnfInstanceState.NOT_INSTANTIATED:
data = _model_non_instantiated_vnf_instance(**updates) data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state data['instantiation_state'] = instantiated_state
vnf_instance_obj = objects.VnfInstance(**data) 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: else:
data = _model_non_instantiated_vnf_instance(**updates) data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state data['instantiation_state'] = instantiated_state
@ -704,6 +739,27 @@ def get_instantiate_vnf_request_with_ext_virtual_links(**updates):
return instantiate_vnf_request 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(): def get_dummy_grant_response():
return {'VDU1': {'checksum': {'algorithm': 'fake algo', return {'VDU1': {'checksum': {'algorithm': 'fake algo',
'hash': 'fake hash'}, 'hash': 'fake hash'},
@ -756,3 +812,210 @@ def wsgi_app_v1(fake_auth_context=None):
uuidsentinel.project_id, is_admin=True) uuidsentinel.project_id, is_admin=True)
api_v1 = InjectContext(ctxt, inner_app_v1) api_v1 = InjectContext(ctxt, inner_app_v1)
return api_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. # under the License.
from unittest import mock from unittest import mock
import ddt import codecs
import os
import ddt
import json
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from six.moves import http_client from six.moves import http_client
import urllib import urllib
import webob
from webob import exc from webob import exc
from tacker.api.vnflcm.v1 import controller from tacker.api.vnflcm.v1 import controller
@ -27,6 +31,7 @@ from tacker.conductor.conductorrpc.vnf_lcm_rpc import VNFLcmRPCAPI
from tacker import context from tacker import context
import tacker.db.vnfm.vnfm_db import tacker.db.vnfm.vnfm_db
from tacker.extensions import nfvo from tacker.extensions import nfvo
from tacker.extensions import vnfm
from tacker.manager import TackerManager from tacker.manager import TackerManager
from tacker import objects from tacker import objects
from tacker.objects import fields from tacker.objects import fields
@ -40,6 +45,13 @@ from tacker.tests import uuidsentinel
from tacker.vnfm import vim_client 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): class FakeVNFMPlugin(mock.Mock):
def __init__(self): def __init__(self):
@ -59,6 +71,100 @@ class FakeVNFMPlugin(mock.Mock):
self.cp32_id = '3d1bd2a2-bf0e-44d1-87af-a2c6b2cad3ed' self.cp32_id = '3d1bd2a2-bf0e-44d1-87af-a2c6b2cad3ed'
self.cp32_update_id = '064c0d99-5a61-4711-9597-2a44dc5da14b' 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 @ddt.ddt
class TestController(base.TestCase): class TestController(base.TestCase):
@ -104,6 +210,27 @@ class TestController(base.TestCase):
return vnf_dict 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(objects.VnfInstance, 'save')
@mock.patch.object(vim_client.VimClient, "get_vim") @mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_package.VnfPackage, 'get_by_id') @mock.patch.object(objects.vnf_package.VnfPackage, 'get_by_id')
@ -1631,3 +1758,30 @@ class TestController(base.TestCase):
'/vnflcm/v1/vnf_instances?' + query) '/vnflcm/v1/vnf_instances?' + query)
self.assertRaises(exceptions.ValidationError, self.assertRaises(exceptions.ValidationError,
self.controller.index, req) 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)