Browse Source

Enhancement of get VNF LCM operation occurrence

- Added support for getting Individual VNF LCM
operation occurrence by its ID

- The query information is enhanced by improving
filtering expressions and operators

- Attributes are also added including (but not
limited to):
  * grant_id
  * _links
  * >retry
  * >fail
  * changedExtConnectivity

- This patch has 2 BP features (Get VNFM LCM Operation
Occurence and attributes of Change External Connectivity
are also added). Since both features are related, they are
merged to one patch. This is why this patch have 2 BPs.

- Filtering for the following attributes:
operationParams, error, resourceChanges and
changedInfo is only limited to the parent
attribute. Currently, child attributes/nested
attributes are not searchable.

Implements: blueprint support-fundamental-lcm
Implements: blueprint support-change-external-connectivity
Spec: https://specs.openstack.org/openstack/tacker-specs/specs/wallaby/support-fundamental-vnf-lcm-based-on-ETSI-NFV.html
Spec: https://specs.openstack.org/openstack/tacker-specs/specs/wallaby/support-change-external-VNF-connectivity-operation.html
Change-Id: Ie9b07c203807d08857be65298d9128b026a8fd37
changes/40/778040/14
Aldinson Esto 1 year ago committed by Aldinson C. Esto
parent
commit
87ea725784
  1. 82
      api-ref/source/v1/samples/vnflcm/list-vnflcm-operation-occurrence-response.json
  2. 10
      api-ref/source/v1/samples/vnflcm/show-vnflcm-operation-occurrence-response.json
  3. 110
      api-ref/source/v1/vnflcm.inc
  4. 12
      tacker/api/common/attribute_filter.py
  5. 20
      tacker/api/views/vnf_lcm.py
  6. 118
      tacker/api/views/vnf_lcm_op_occs.py
  7. 37
      tacker/api/vnflcm/v1/controller.py
  8. 12
      tacker/api/vnflcm/v1/router.py
  9. 2
      tacker/db/db_sqlalchemy/models.py
  10. 38
      tacker/db/migration/alembic_migrations/versions/3adac34764da_add_column_to_vnf_lcm_op_occs.py
  11. 2
      tacker/db/migration/alembic_migrations/versions/HEAD
  12. 3
      tacker/objects/fields.py
  13. 181
      tacker/objects/vnf_lcm_op_occs.py
  14. 11
      tacker/policies/vnf_lcm.py
  15. 8
      tacker/tests/functional/sol/vnflcm/base.py
  16. 53
      tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py
  17. 20
      tacker/tests/unit/objects/fakes.py
  18. 3
      tacker/tests/unit/objects/test_vnf_lcm_op_occs.py
  19. 64
      tacker/tests/unit/vnflcm/fakes.py
  20. 138
      tacker/tests/unit/vnflcm/test_controller.py

82
api-ref/source/v1/samples/vnflcm/list-vnflcm-operation-occurrence-response.json

@ -0,0 +1,82 @@
[
{
"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",
"grantId": "3432cebe-db0a-11e8-9023-005056317abe",
"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":"http://sample.com/grant/v1/grants/3432cebe-db0a-11e8-9023-005056317abe"
},
"retry":{
"href":"http://sample1.com/vnflcm/v1/vnf_lcm_op_occs/d85c6ae4-af16-42c0-96fc-82f7c014c468/retry"
},
"rollback":{
"href":"http://sample1.com/vnflcm/v1/vnf_lcm_op_occs/d85c6ae4-af16-42c0-96fc-82f7c014c468/rollback"
},
"fail":{
"href":"http://sample1.com/vnflcm/v1/vnf_lcm_op_occs/d85c6ae4-af16-42c0-96fc-82f7c014c468/fail"
}
}
}
]

10
api-ref/source/v1/samples/vnflcm/show-vnflcm-operation-occurrence-response.json

@ -4,6 +4,7 @@
"stateEnteredTime": "2020-08-02T06:50:50.883373",
"startTime": "2020-08-02T06:41:34.883483",
"vnfInstanceId": "0b7b95a9-21d5-4ac4-80c8-9ae9f7323787",
"grantId": "3432cebe-db0a-11e8-9023-005056317abe",
"operation": "INSTANTIATE",
"isAutomaticInvocation": false,
"operationParams": "{
@ -65,6 +66,15 @@
},
"grant": {
"href": "/grant/v1/grants/3432cebe-db0a-11e8-9023-005056317abe"
},
"retry":{
"href":"http://sample1.com/vnflcm/v1/vnf_lcm_op_occs/d85c6ae4-af16-42c0-96fc-82f7c014c468/retry"
},
"rollback":{
"href":"http://sample1.com/vnflcm/v1/vnf_lcm_op_occs/d85c6ae4-af16-42c0-96fc-82f7c014c468/rollback"
},
"fail":{
"href":"http://sample1.com/vnflcm/v1/vnf_lcm_op_occs/d85c6ae4-af16-42c0-96fc-82f7c014c468/fail"
}
}
}

110
api-ref/source/v1/vnflcm.inc

@ -712,6 +712,7 @@ Response Parameters
- stateEnteredTime: state_entered_time
- startTime: start_time
- vnfInstanceId: vnf_lcm_vnf_instance_id
- grantId: grant_id
- operation: operation
- isAutomaticInvocation: is_automatic_invocation
- operationParams: operation_params
@ -769,6 +770,19 @@ Response Parameters
- vnfProductName: changed_info_vnf_product_name
- vnfSotwareVersion: changed_info_vnf_sotware_version
- vnfdVersion: changed_info_vnfd_version
- changedExtConnectivity: changed_ext_connectivity
- id: changed_ext_connectivity_id
- resourceHandle: resource_handle
- vimConnectionId: vim_connection_id
- resourceId: resource_handle_resource_id
- vimLevelResourceType: resource_handle_vim_level_resource_type
- extLinkPorts: ext_link_ports
- id: ext_link_port_id
- resourceHandle: resource_handle
- vimConnectionId: vim_connection_id
- resourceId: resource_handle_resource_id
- vimLevelResourceType: resource_handle_vim_level_resource_type
- cpInstanceId: cp_instance_id
- _links: vnf_instance_links
Response Example
@ -777,6 +791,102 @@ Response Example
.. literalinclude:: samples/vnflcm/show-vnflcm-operation-occurrence-response.json
:language: javascript
List VNF LCM operation occurrence
=================================
.. rest_method:: GET /vnflcm/v1/vnf_lcm_op_occs
The API consumer can use this method to query status information about multiple VNF lifecycle management operation
occurrences.
Response Codes
--------------
.. rest_status_code:: success status.yaml
- 200
.. rest_status_code:: error status.yaml
- 400
- 403
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
- grantId: grant_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/list-vnflcm-operation-occurrence-response.json
:language: javascript
Roll back a VNF lifecycle operation
===================================

12
tacker/api/common/attribute_filter.py

@ -163,6 +163,18 @@ def _parse_filter(filter_rule):
try:
tokens = filter_rule.split(',')
filter_type = None
# TODO(esto-aln): This condition and the lines below will be removed
# if JSON will be supported via OR Mapping. Currently this condition
# allows support of JSON strings '"{...}"' for filtering
if (tokens[2].startswith("'\"{") and
tokens[len(tokens) - 1].endswith("}\"'")):
tokens[2] = ','.join(tokens[2:])
# retain first 3 indices and remove the rest
# to process as string
tokens = tokens[0:3]
if len(tokens) >= 3:
if tokens[0] in _filters.SUPPORTED_OP_ONE:
filter_type = 'simple_filter_expr_one'

20
tacker/api/views/vnf_lcm.py

@ -89,6 +89,8 @@ class ViewBuilder(base.BaseViewBuilder):
return vim_connections
# TODO(esto-aln): This method will be transferred to
# tacker/api/views/vnf_lcm_op_occs.py in the future
def _get_lcm_op_occs_links(self, vnf_lcm_op_occs):
_links = {
"self": {
@ -101,6 +103,12 @@ class ViewBuilder(base.BaseViewBuilder):
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.vnf_instance_id}
},
"retry": {
"href":
'%(endpoint)s/vnflcm/v1/vnf_lcm_op_occs/%(id)s/retry'
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.id}
},
"rollback": {
"href":
'%(endpoint)s/vnflcm/v1/vnf_lcm_op_occs/%(id)s/rollback'
@ -111,6 +119,12 @@ class ViewBuilder(base.BaseViewBuilder):
"href": '%(endpoint)s/vnflcm/v1/vnf_lcm_op_occs/%(id)s/grant'
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.id}
},
"fail": {
"href":
'%(endpoint)s/vnflcm/v1/vnf_lcm_op_occs/%(id)s/fail'
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.id}
}
}
@ -129,11 +143,13 @@ class ViewBuilder(base.BaseViewBuilder):
vnf_instance_dict.update(links)
return vnf_instance_dict
# TODO(esto-aln): This method will be transferred to
# tacker/api/views/vnf_lcm_op_occs.py in the future
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)
vnf_lcm_op_occs_dict.pop('errorPoint')
links = self._get_lcm_op_occs_links(vnf_lcm_op_occs)
@ -257,5 +273,7 @@ class ViewBuilder(base.BaseViewBuilder):
def subscription_show(self, vnf_lcm_subscriptions):
return self._get_vnf_lcm_subscription(vnf_lcm_subscriptions)
# TODO(esto-aln): This method will be transferred to
# tacker/api/views/vnf_lcm_op_occs.py in the future
def show_lcm_op_occs(self, vnf_lcm_op_occs):
return self._get_vnf_lcm_op_occs(vnf_lcm_op_occs)

118
tacker/api/views/vnf_lcm_op_occs.py

@ -0,0 +1,118 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from tacker.api import views as base
from tacker.common import utils
import tacker.conf
from tacker.objects import vnf_lcm_op_occs as _vnf_lcm_op_occs
CONF = tacker.conf.CONF
LOG = logging.getLogger(__name__)
class ViewBuilder(base.BaseViewBuilder):
FLATTEN_ATTRIBUTES = _vnf_lcm_op_occs.VnfLcmOpOcc.FLATTEN_ATTRIBUTES
COMPLEX_ATTRIBUTES = _vnf_lcm_op_occs.VnfLcmOpOcc.COMPLEX_ATTRIBUTES
FLATTEN_COMPLEX_ATTRIBUTES = [key for key in FLATTEN_ATTRIBUTES.keys()
if '/' in key]
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}
},
"retry": {
"href":
'%(endpoint)s/vnflcm/v1/vnf_lcm_op_occs/%(id)s/retry'
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.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}
},
"fail": {
"href":
'%(endpoint)s/vnflcm/v1/vnf_lcm_op_occs/%(id)s/fail'
% {"endpoint": CONF.vnf_lcm.endpoint_url,
"id": vnf_lcm_op_occs.id}
}
}
return {"_links": _links}
def _get_vnf_lcm_op_occs_list(self, vnf_lcm_op_occs, include_fields=None):
vnf_lcm_op_occs_dict = vnf_lcm_op_occs.to_dict(
include_fields=include_fields)
vnf_lcm_op_occs_dict = utils.convert_snakecase_to_camelcase(
vnf_lcm_op_occs_dict)
vnf_lcm_op_occs_dict.pop('errorPoint', None)
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 index(self, request, vnf_lcm_op_occs, all_fields=True,
exclude_fields=None, fields=None, exclude_default=False):
# Find out which fields are to be returned in the response.
if all_fields:
include_fields = set(self.FLATTEN_ATTRIBUTES.keys())
if fields:
fields = set(fields.split(','))
attributes = set(self.COMPLEX_ATTRIBUTES).intersection(fields)
for attribute in attributes:
add_fields = set([key for key in self.FLATTEN_ATTRIBUTES.
keys() if key.startswith(attribute)])
fields = fields.union(add_fields)
include_fields = set(
_vnf_lcm_op_occs.VnfLcmOpOcc.SIMPLE_ATTRIBUTES).union(fields)
elif exclude_default:
include_fields = set(
_vnf_lcm_op_occs.VnfLcmOpOcc.SIMPLE_ATTRIBUTES)
elif exclude_fields:
exclude_fields = set(exclude_fields.split(','))
exclude_additional_attributes = set(
self.COMPLEX_ATTRIBUTES).intersection(exclude_fields)
for attribute in exclude_additional_attributes:
fields = set([key for key in self.FLATTEN_ATTRIBUTES.keys()
if key.startswith(attribute)])
exclude_fields = exclude_fields.union(fields)
include_fields = set(self.FLATTEN_ATTRIBUTES.keys()) - \
exclude_fields
return [
self._get_vnf_lcm_op_occs_list(
vnf_lcm_op_occ, include_fields=include_fields)
for vnf_lcm_op_occ in vnf_lcm_op_occs]

37
tacker/api/vnflcm/v1/controller.py

@ -42,6 +42,7 @@ from tacker._i18n import _
from tacker.api.schemas import vnf_lcm
from tacker.api import validation
from tacker.api.views import vnf_lcm as vnf_lcm_view
from tacker.api.views import vnf_lcm_op_occs as vnf_op_occs_view
from tacker.api.vnflcm.v1 import sync_resource
from tacker.common import exceptions
from tacker.common import utils
@ -52,6 +53,7 @@ from tacker.extensions import vnfm
from tacker import manager
from tacker import objects
from tacker.objects import fields
from tacker.objects import vnf_lcm_op_occs as vnf_lcm_op_occs_obj
from tacker.objects import vnf_lcm_subscriptions as subscription_obj
from tacker.plugins.common import constants
from tacker.policies import vnf_lcm as vnf_lcm_policies
@ -185,6 +187,7 @@ class VnfLcmController(wsgi.Controller):
super(VnfLcmController, self).__init__()
self.rpc_api = vnf_lcm_rpc.VNFLcmRPCAPI()
self._vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM']
self._view_builder_op_occ = vnf_op_occs_view.ViewBuilder()
def _get_vnf_instance_href(self, vnf_instance):
return '/vnflcm/v1/vnf_instances/%s' % vnf_instance.id
@ -1503,6 +1506,40 @@ class VnfLcmController(wsgi.Controller):
return self._make_problem_detail(error_msg,
500, title='Internal Server Error')
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.BAD_REQUEST))
def list_lcm_op_occs(self, request):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'list_lcm_op_occs')
all_fields = request.GET.get('all_fields')
exclude_default = request.GET.get('exclude_default')
fields = request.GET.get('fields')
exclude_fields = request.GET.get('exclude_fields')
filters = request.GET.get('filter')
if not (all_fields or fields or exclude_fields):
exclude_default = True
self._view_builder_op_occ.validate_attribute_fields(
all_fields=all_fields, fields=fields,
exclude_fields=exclude_fields,
exclude_default=exclude_default)
filters = self._view_builder_op_occ.validate_filter(filters)
try:
vnf_lcm_op_occs = \
vnf_lcm_op_occs_obj.VnfLcmOpOccList.get_by_filters(
request.context, read_deleted='no', filters=filters)
except Exception as e:
LOG.exception(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
return self._view_builder_op_occ.index(request, vnf_lcm_op_occs,
all_fields=all_fields, exclude_fields=exclude_fields,
fields=fields, exclude_default=exclude_default)
def _make_problem_detail(
self,
detail,

12
tacker/api/vnflcm/v1/router.py

@ -138,3 +138,15 @@ class VnflcmAPIRouter(wsgi.Router):
methods = {"GET": "subscription_show", "DELETE": "delete_subscription"}
self._setup_route(mapper, "/subscriptions/{subscriptionId}",
methods, controller, default_resource)
# {apiRoot}/vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId}/retry resource
methods = {"POST": "retry"}
self._setup_route(mapper,
"/vnf_lcm_op_occs/{id}/retry",
methods, controller, default_resource)
# Allowed methods on
# {apiRoot}/vnflcm/v1/vnf_lcm_op_occs resource
methods = {"GET": "list_lcm_op_occs"}
self._setup_route(mapper, "/vnf_lcm_op_occs",
methods, controller, default_resource)

2
tacker/db/db_sqlalchemy/models.py

@ -305,6 +305,7 @@ class VnfLcmOpOccs(model_base.BASE, models.SoftDeleteMixin,
vnf_instance_id = sa.Column(sa.String(36),
sa.ForeignKey('vnf_instances.id'),
nullable=False)
grant_id = sa.Column(sa.String(36), nullable=True)
state_entered_time = sa.Column(sa.DateTime(), nullable=False)
start_time = sa.Column(sa.DateTime(), nullable=False)
operation_state = sa.Column(sa.String(length=255), nullable=False)
@ -315,6 +316,7 @@ class VnfLcmOpOccs(model_base.BASE, models.SoftDeleteMixin,
error = sa.Column(sa.JSON(), nullable=True)
resource_changes = sa.Column(sa.JSON(), nullable=True)
changed_info = sa.Column(sa.JSON(), nullable=True)
changed_ext_connectivity = sa.Column(sa.JSON(), nullable=True)
error_point = sa.Column(sa.Integer, nullable=False)

38
tacker/db/migration/alembic_migrations/versions/3adac34764da_add_column_to_vnf_lcm_op_occs.py

@ -0,0 +1,38 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# flake8: noqa: E402
"""add_column_to_vnf_lcm_op_occs
Revision ID: 3adac34764da
Revises: 62d18199909e
Create Date: 2021-02-16 16:19:12.100380
"""
# revision identifiers, used by Alembic.
revision = '3adac34764da'
down_revision = '7186440a306b'
from alembic import op
import sqlalchemy as sa
from tacker.db import migration
def upgrade(active_plugins=None, options=None):
op.add_column('vnf_lcm_op_occs',
sa.Column('grant_id', sa.VARCHAR(length=36), nullable=True))
op.add_column('vnf_lcm_op_occs',
sa.Column('changed_ext_connectivity', sa.JSON(), nullable=True))

2
tacker/db/migration/alembic_migrations/versions/HEAD

@ -1 +1 @@
7186440a306b
3adac34764da

3
tacker/objects/fields.py

@ -225,8 +225,7 @@ class LcmOccsOperationState(BaseTackerEnum):
FAILED_TEMP = 'FAILED_TEMP'
FAILED = 'FAILED'
ALL = (STARTING, PROCESSING, COMPLETED,
FAILED_TEMP, FAILED)
ALL = (STARTING, PROCESSING, COMPLETED, FAILED_TEMP, FAILED)
class LcmOccsOperationType(BaseTackerEnum):

181
tacker/objects/vnf_lcm_op_occs.py

@ -15,10 +15,13 @@ from datetime import datetime
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from oslo_versionedobjects import base as ovoo_base
from sqlalchemy import exc
from sqlalchemy.orm import joinedload
from sqlalchemy_filters import apply_filters
from tacker.common import exceptions
from tacker.common import utils
from tacker.db import api as db_api
from tacker.db.db_sqlalchemy import api
from tacker.db.db_sqlalchemy import models
@ -55,11 +58,34 @@ def _vnf_lcm_op_occ_update(context, values):
if values.changed_info:
update.update({'changed_info': jsonutils.dumps(
values.changed_info.to_dict())})
if 'changed_ext_connectivity' in values:
if values.changed_ext_connectivity:
update.update({'changed_ext_connectivity': jsonutils.dumps(
[chg_ext_conn.to_dict() for chg_ext_conn in
values.changed_ext_connectivity])})
api.model_query(context, models.VnfLcmOpOccs). \
filter_by(id=values.id). \
update(update, synchronize_session=False)
def _make_vnf_lcm_op_occs_list(context, op_occ_list,
db_op_occ_list):
lcm_op_occ_class = VnfLcmOpOcc
op_occ_list.objects = []
for db_op_occ in db_op_occ_list:
if(db_op_occ['changed_info'] and
isinstance(db_op_occ['changed_info'], str)):
db_op_occ['changed_info'] = jsonutils.loads(
db_op_occ['changed_info'])
vnf_lcm_op_occ_obj = lcm_op_occ_class._from_db_object(
context, lcm_op_occ_class(context), db_op_occ)
op_occ_list.objects.append(vnf_lcm_op_occ_obj)
op_occ_list.obj_reset_changes()
return op_occ_list
@db_api.context_manager.reader
def _vnf_lcm_op_occs_get_by_id(context, vnf_lcm_op_occ_id):
@ -91,6 +117,19 @@ def _vnf_lcm_op_occs_get_by_vnf_instance_id(context, vnf_instance_id):
return result
@db_api.context_manager.reader
def _vnf_lcm_op_occs_get_by_filters(context, read_deleted=None,
filters=None):
query = api.model_query(context, models.VnfLcmOpOccs,
read_deleted=read_deleted, project_only=True)
if filters:
query = apply_filters(query, filters)
return query.all()
@db_api.context_manager.reader
def _vnf_notify_get_by_id(context, vnf_instance_id, columns_to_join=None):
@ -170,6 +209,7 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat,
'state_entered_time': fields.DateTimeField(nullable=False),
'start_time': fields.DateTimeField(nullable=False),
'vnf_instance_id': fields.StringField(nullable=False),
'grant_id': fields.StringField(nullable=True),
'operation': fields.StringField(nullable=False),
'is_automatic_invocation': fields.BooleanField(default=False),
'operation_params': fields.StringField(nullable=True),
@ -180,9 +220,40 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat,
'ResourceChanges', nullable=True, default=None),
'changed_info': fields.ObjectField(
'VnfInfoModifications', nullable=True, default=None),
'changed_ext_connectivity': fields.ListOfObjectsField(
'ExtVirtualLinkInfo', nullable=True, default=[]),
'error_point': fields.IntegerField(nullable=True, default=0)
}
ALL_ATTRIBUTES = {
'id': ('id', 'string', 'VnfLcmOpOccs'),
'operationState': ('operation_state', 'string', 'VnfLcmOpOccs'),
'stateEnteredTime':
('state_entered_time', 'datetime', 'VnfLcmOpOccs'),
'startTime': ('start_time', 'datetime', 'VnfLcmOpOccs'),
'vnfInstanceId': ('vnf_instance_id', 'string', 'VnfLcmOpOccs'),
'grantId': ('grant_id', 'string', 'VnfLcmOpOccs'),
'operation': ('operation', 'string', 'VnfLcmOpOccs'),
'isAutomaticInvocation':
('is_automatic_invocation', 'boolean', 'VnfLcmOpOccs'),
'isCancelPending': ('is_cancel_pending', 'string', 'VnfLcmOpOccs'),
'errorPoint': ('error_point', 'number', 'VnfLcmOpOccs'),
'operationParams': ('operation_params', 'string', 'VnfLcmOpOccs'),
'error': ('error', 'string', 'VnfLcmOpOccs'),
'resourceChanges': ('resource_changes', 'string', 'VnfLcmOpOccs'),
'changedInfo': ('changed_info', 'string', 'VnfLcmOpOccs')
}
FLATTEN_ATTRIBUTES = utils.flatten_dict(ALL_ATTRIBUTES.copy())
SIMPLE_ATTRIBUTES = ['id', 'operationState', 'stateEnteredTime',
'startTime', 'vnfInstanceId', 'grantId', 'operation',
'isAutomaticInvocation',
'isCancelPending', 'errorPoint']
COMPLEX_ATTRIBUTES = ['error', 'resourceChanges', 'changedInfo',
'operationParams', 'changedExtConnectivity']
@base.remotable
def create(self):
updates = self.obj_clone()
@ -197,7 +268,9 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat,
def _from_db_object(context, vnf_lcm_op_occ_obj, db_vnf_lcm_op_occ):
special_fields = ['error',
'resource_changes', 'changed_info']
'resource_changes',
'changed_info',
'changed_ext_connectivity']
for key in vnf_lcm_op_occ_obj.fields:
if key in special_fields:
continue
@ -214,6 +287,12 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat,
changed_info = VnfInfoModifications.obj_from_primitive(
db_vnf_lcm_op_occ['changed_info'], context)
vnf_lcm_op_occ_obj.changed_info = changed_info
if db_vnf_lcm_op_occ['changed_ext_connectivity']:
changed_ext_conn = \
[objects.ExtVirtualLinkInfo.obj_from_primitive(
chg_ext_conn, context) for chg_ext_conn in
db_vnf_lcm_op_occ['changed_ext_connectivity']]
vnf_lcm_op_occ_obj.changed_ext_connectivity = changed_ext_conn
vnf_lcm_op_occ_obj._context = context
vnf_lcm_op_occ_obj.obj_reset_changes()
@ -238,6 +317,11 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat,
obj_data = VnfInfoModifications._from_dict(
primitive.get('changed_info'))
primitive.update({'changed_info': obj_data})
if 'changed_ext_connectivity' in primitive.keys():
obj_data = [objects.ExtVirtualLinkInfo.obj_from_primitive(
chg_ext_conn, context) for chg_ext_conn in
primitive.get('changed_ext_connectivity')]
primitive.update({'changed_ext_connectivity': obj_data})
vnf_lcm_op_occ = VnfLcmOpOcc._from_dict(primitive)
return vnf_lcm_op_occ
@ -252,6 +336,7 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat,
state_entered_time = data_dict.get('state_entered_time')
start_time = data_dict.get('start_time')
vnf_instance_id = data_dict.get('vnf_instance_id')
grant_id = data_dict.get('grant_id')
operation = data_dict.get('operation')
is_automatic_invocation = data_dict.get('is_automatic_invocation')
operation_params = data_dict.get('operation_params')
@ -259,12 +344,14 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat,
error = data_dict.get('error')
resource_changes = data_dict.get('resource_changes')
changed_info = data_dict.get('changed_info')
changed_ext_connectivity = data_dict.get('changed_ext_connectivity')
error_point = data_dict.get('error_point')
obj = cls(operation_state=operation_state,
state_entered_time=state_entered_time,
start_time=start_time,
vnf_instance_id=vnf_instance_id,
grant_id=grant_id,
operation=operation,
is_automatic_invocation=is_automatic_invocation,
operation_params=operation_params,
@ -272,28 +359,76 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat,
error=error,
resource_changes=resource_changes,
changed_info=changed_info,
changed_ext_connectivity=changed_ext_connectivity,
error_point=error_point
)
return obj
def to_dict(self):
data = {'id': self.id,
'operation_state': self.operation_state,
'state_entered_time': self.state_entered_time,
'start_time': self.start_time,
'vnf_instance_id': self.vnf_instance_id,
'operation': self.operation,
'is_automatic_invocation': self.is_automatic_invocation,
'operation_params': self.operation_params,
'is_cancel_pending': self.is_cancel_pending,
'error_point': self.error_point}
def _get_error(self, include_fields=None):
key = 'error'
if key in include_fields:
return {key: self.error.to_dict()}
def _get_resource_changes(self, include_fields=None):
key = 'resourceChanges'
if key in include_fields:
return {key: self.resource_changes.to_dict()}
def _get_changed_info(self, include_fields=None):
key = 'changedInfo'
if key in include_fields:
return {key: self.changed_info.to_dict()}
def _get_operation_params(self, include_fields=None):
key = 'operationParams'
if key in include_fields:
return {key: self.operation_params}
def _get_changed_ext_connectivity(self, include_fields=None):
key = 'changedExtConnectivity'
return {key: [chg_ext_conn.to_dict() for chg_ext_conn in
self.changed_ext_connectivity]}
def to_dict(self, include_fields=None):
data = dict()
if not include_fields:
include_fields = set(self.FLATTEN_ATTRIBUTES.keys())
# add simple fields
to_fields = set(self.SIMPLE_ATTRIBUTES).intersection(include_fields)
for field in to_fields:
data[field] = getattr(self, self.FLATTEN_ATTRIBUTES[field][0])
# add complex attributes
if self.error:
data.update({'error': self.error.to_dict()})
error = self._get_error(include_fields=include_fields)
if error:
data.update(error)
if self.resource_changes:
data.update({'resource_changes': self.resource_changes.to_dict()})
resource_changes = self._get_resource_changes(
include_fields=include_fields)
if resource_changes:
data.update(resource_changes)
if self.changed_info:
data.update({'changed_info': self.changed_info.to_dict()})
changed_info = self._get_changed_info(
include_fields=include_fields)
if changed_info:
data.update(changed_info)
if self.operation_params:
operation_params = self._get_operation_params(
include_fields=include_fields)
if operation_params:
data.update(operation_params)
if self.changed_ext_connectivity:
changed_ext_connectivity = self._get_changed_ext_connectivity(
include_fields=include_fields)
if changed_ext_connectivity:
data.update(changed_ext_connectivity)
return data
@ -309,6 +444,22 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat,
return cls._from_db_object(context, cls(), db_vnf_lcm_op_occs)
@base.TackerObjectRegistry.register
class VnfLcmOpOccList(ovoo_base.ObjectListBase, base.TackerObject):
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('VnfLcmOpOcc')
}
@base.remotable_classmethod
def get_by_filters(cls, context, read_deleted=None, filters=None):
db_vnf_lcm_op_occs = _vnf_lcm_op_occs_get_by_filters(
context, read_deleted=read_deleted, filters=filters)
return _make_vnf_lcm_op_occs_list(context, cls(), db_vnf_lcm_op_occs)
@base.TackerObjectRegistry.register
class ResourceChanges(base.TackerObject,
base.TackerPersistentObject):

11
tacker/policies/vnf_lcm.py

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

8
tacker/tests/functional/sol/vnflcm/base.py

@ -487,6 +487,14 @@ class BaseVnfLcmTest(base.BaseTackerTest):
return resp, response_body
def _list_op_occs(self, filter_string=''):
show_url = os.path.join(
self.base_vnf_lcm_op_occs_url)
resp, response_body = self.http_client.do_request(
show_url + filter_string, "GET")
return resp, response_body
def _wait_terminate_vnf_instance(self, id, timeout=None):
start_time = int(time.time())

53
tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py

@ -778,6 +778,8 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
- Get vnflcmOpOccId to retry.
- Retry instantiation operation.
- Get opOccs information.
- Get opOccs list.
- Delete VNF instance.
- Delete subscription.
"""
# Create subscription and register it.
@ -855,6 +857,10 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id)
self._assert_occ_show(resp, op_occs_info)
# occ-list
resp, op_occs_info = self._list_op_occs()
self._assert_occ_list(resp, op_occs_info)
# Delete VNF
resp, _ = self._delete_vnf_instance(vnf_instance_id)
self._wait_lcm_done(vnf_instance_id=vnf_instance_id)
@ -877,7 +883,9 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
- Get vnfcmOpOccId to retry.
- Retry Scale-Out operation.
- Get opOccs information.
- Terminate VNF.
- Get opOccs list.
- Terminate VNF instance.
- Delete VNF instance.
- Delete subscription.
"""
# Create subscription and register it.
@ -964,6 +972,10 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id)
self._assert_occ_show(resp, op_occs_info)
# occ-list
resp, op_occs_info = self._list_op_occs()
self._assert_occ_list(resp, op_occs_info)
# Terminate VNF
stack = self._get_heat_stack(vnf_instance_id)
resources_list = self._get_heat_resource_list(stack.id)
@ -1000,6 +1012,7 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
- Get vnflcmOpOccId to rollback.
- Rollback instantiation operation.
- Get opOccs information.
- Get opOccs list
- Delete subscription.
"""
# Create subscription and register it.
@ -1072,6 +1085,10 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id)
self._assert_occ_show(resp, op_occs_info)
# occ-list
resp, op_occs_info = self._list_op_occs()
self._assert_occ_list(resp, op_occs_info)
# Delete VNF
resp, _ = self._delete_vnf_instance(vnf_instance_id)
self._wait_lcm_done(vnf_instance_id=vnf_instance_id)
@ -1094,6 +1111,7 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
- Get vnfcmOpOccId to rollback.
- Rollback Scale-Out operation.
- Get opOccs information.
- get opOccs List.
- Terminate VNF.
- Delete subscription.
"""
@ -1176,6 +1194,10 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id)
self._assert_occ_show(resp, op_occs_info)
# occ-list
resp, op_occs_info = self._list_op_occs()
self._assert_occ_list(resp, op_occs_info)
# Terminate VNF
stack = self._get_heat_stack(vnf_instance_id)
resources_list = self._get_heat_resource_list(stack.id)
@ -1283,6 +1305,10 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id)
self._assert_occ_show(resp, op_occs_info)
# occ-list
resp, op_occs_info = self._list_op_occs()
self._assert_occ_list(resp, op_occs_info)
# Delete Stack
stack = self._get_heat_stack(vnf_instance_id)
self._delete_heat_stack(stack.id)
@ -1391,6 +1417,10 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id)
self._assert_occ_show(resp, op_occs_info)
# occ-list
resp, op_occs_info = self._list_op_occs()
self._assert_occ_list(resp, op_occs_info)
# Terminate VNF
stack = self._get_heat_stack(vnf_instance_id)
resources_list = self._get_heat_resource_list(stack.id)
@ -1562,3 +1592,24 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
self.assertIsNotNone(_links.get('vnfInstance').get('href'))
self.assertIsNotNone(_links.get('grant'))
self.assertIsNotNone(_links.get('grant').get('href'))
def _assert_occ_list(self, resp, op_occs_list):
self.assertEqual(200, resp.status_code)
# Only check required parameters.
for op_occs_info in op_occs_list:
self.assertIsNotNone(op_occs_info.get('id'))
self.assertIsNotNone(op_occs_info.get('operationState'))
self.assertIsNotNone(op_occs_info.get('stateEnteredTime'))
self.assertIsNotNone(op_occs_info.get('vnfInstanceId'))
self.assertIsNotNone(op_occs_info.get('operation'))
self.assertIsNotNone(op_occs_info.get('isAutomaticInvocation'))
self.assertIsNotNone(op_occs_info.get('isCancelPending'))
_links = op_occs_info.get('_links')
self.assertIsNotNone(_links.get('self'))
self.assertIsNotNone(_links.get('self').get('href'))
self.assertIsNotNone(_links.get('vnfInstance'))
self.assertIsNotNone(_links.get('vnfInstance').get('href'))
self.assertIsNotNone(_links.get('grant'))
self.assertIsNotNone(_links.get('grant').get('href'))

20
tacker/tests/unit/objects/fakes.py

@ -512,3 +512,23 @@ def get_vnf(vnfd_id, vim_id):
'placement_attr': "test_placement_attr",
'vim_id': vim_id
}
def get_changed_ext_conn_data():
return [{
"id": uuidsentinel.change_ext_conn_id,
"resource_handle": {
"vim_connection_id": uuidsentinel.vim_connection_id,
"resource_id": uuidsentinel.vl_resource_id,
"vim_level_resource_type": "OS::Neutron::Net",
},
"ext_link_ports": [{
"id": uuidsentinel.ext_link_ports_id,
"resource_handle": {
"vim_connection_id": uuidsentinel.vim_connection_id,
"resource_id": uuidsentinel.port_resource_id,
"vim_level_resource_type": "OS::Neutron::Port",
},
"cp_instance_id": uuidsentinel.cp_instance_id,
}]
}]

3
tacker/tests/unit/objects/test_vnf_lcm_op_occs.py

@ -90,10 +90,13 @@ class TestVnfLcmOpOcc(SqlTestCase):
problem_obj.detail = 'test_err'
changed_info = objects.vnf_lcm_op_occs.VnfInfoModifications(
context=self.context, **fakes.get_changed_info_data())
changed_ext_conn = [objects.ExtVirtualLinkInfo.obj_from_primitive(
i, self.context) for i in fakes.get_changed_ext_conn_data()]
vnf_lcm_op_occs.operation_state = 'FAILED_TEMP'
vnf_lcm_op_occs.error = problem_obj
vnf_lcm_op_occs.id = uuidsentinel.vnf_lcm_op_occs_id
vnf_lcm_op_occs.changed_info = changed_info
vnf_lcm_op_occs.changed_ext_connectivity = changed_ext_conn
vnf_lcm_op_occs.save()
self.assertEqual('FAILED_TEMP', vnf_lcm_op_occs.operation_state)

64
tacker/tests/unit/vnflcm/fakes.py

@ -1265,20 +1265,28 @@ VNFLCMOPOCC_RESPONSE = {
"href": CONF.vnf_lcm.endpoint_url + '/vnflcm/v1/vnf_instances/'
'f26f181d-7891-4720-b022-b074ec1733ef'
},
"retry": {
"href": CONF.vnf_lcm.endpoint_url + '/vnflcm/v1/vnf_lcm_op_occs/'
'f26f181d-7891-4720-b022-b074ec1733ef/retry'
},
"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'
}},
'f26f181d-7891-4720-b022-b074ec1733ef/grant',
},
"fail": {
"href": CONF.vnf_lcm.endpoint_url + '/vnflcm/v1/vnf_lcm_op_occs/'
'f26f181d-7891-4720-b022-b074ec1733ef/fail'}},
'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',
'grantId': 'f26f181d-7891-4720-b022-b074ec1733ef',
'operation': 'MODIFY_INFO',
'isAutomaticInvocation': False,
'operationParams': '{"is_reverse": False, "is_auto": False}',
@ -1336,7 +1344,24 @@ VNFLCMOPOCC_RESPONSE = {
'vnfProductName': 'fake_vnf_product_name',
'vnfSoftwareVersion': 'fake_vnf_software_version',
'vnfdVersion': 'fake_vnfd_version'
}
},
"changedExtConnectivity": [{
"id": constants.UUID,
"resourceHandle": {
"vimConnectionId": constants.UUID,
"resourceId": constants.UUID,
"vimLevelResourceType": "OS::Neutron::Net",
},
"extLinkPorts": [{
"id": constants.UUID,
"resourceHandle": {
"vimConnectionId": constants.UUID,
"resourceId": constants.UUID,
"vimLevelResourceType": "OS::Neutron::Port",
},
"cpInstanceId": constants.UUID,
}]
}]
}
VNFLCMOPOCC_INDEX_RESPONSE = [VNFLCMOPOCC_RESPONSE]
@ -1436,6 +1461,28 @@ def fake_vnf_lcm_op_occs():
}
changed_info_obj = objects.VnfInfoModifications(**changed_info)
changed_ext_connectivity = [{
"id": constants.UUID,
"resource_handle": {
"vim_connection_id": constants.UUID,
"resource_id": constants.UUID,
"vim_level_resource_type": "OS::Neutron::Net",
},
"ext_link_ports": [{
"id": constants.UUID,
"resource_handle": {
"vim_connection_id": constants.UUID,
"resource_id": constants.UUID,
"vim_level_resource_type": "OS::Neutron::Port",
},
"cp_instance_id": constants.UUID,
}]
}]
changed_ext_connectivity_obj = \
[objects.ExtVirtualLinkInfo.obj_from_primitive(
chg_ext_conn, context) for chg_ext_conn in
changed_ext_connectivity]
dt = datetime.datetime(1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
vnf_lcm_op_occs = {
'id': constants.UUID,
@ -1443,13 +1490,15 @@ def fake_vnf_lcm_op_occs():
'state_entered_time': dt,
'start_time': dt,
'vnf_instance_id': constants.UUID,
'grant_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
'changed_info': changed_info_obj,
'changed_ext_connectivity': changed_ext_connectivity_obj,
}
return vnf_lcm_op_occs
@ -1484,3 +1533,10 @@ def vnf_data(status='ACTIVE'):
name='test',
status=status,
vim_id=uuidsentinel.vim_id)
def return_vnf_lcm_opoccs_list():
vnf_lcm_op_occs = fake_vnf_lcm_op_occs()
obj = objects.VnfLcmOpOcc(**vnf_lcm_op_occs)
return [obj]

138
tacker/tests/unit/vnflcm/test_controller.py

@ -3366,3 +3366,141 @@ class TestController(base.TestCase):
resp = req.get_response(self.app)
self.assertEqual(http_client.INTERNAL_SERVER_ERROR, resp.status_code)
@mock.patch.object(objects.VnfLcmOpOccList, "get_by_filters")
def test_op_occ_list(self, mock_op_occ_list):
req = fake_request.HTTPRequest.blank('/vnflcm/v1/vnf_lcm_op_occs')
complex_attributes = [
'error',
'resourceChanges',
'operationParams',
'changedInfo']
vnf_lcm_op_occ = fakes.return_vnf_lcm_opoccs_list()
expected_result = fakes.index_response(
remove_attrs=complex_attributes)
mock_op_occ_list.return_value = vnf_lcm_op_occ
res_dict = self.controller.list_lcm_op_occs(req)
self.assertEqual(expected_result, res_dict)
@mock.patch.object(objects.VnfLcmOpOccList, "get_by_filters")
@ddt.data(
{'filter': '(eq,id,f26f181d-7891-4720-b022-b074ec1733ef)'},
{'filter': '(neq,operationState,COMPLETED)'},
{'filter': '(gte,stateEnteredTime,2020-03-14 04:10:15+00:00)'},
{'filter': '(eq,isAutomaticInvocation,False)'},
{'filter': '(lt,errorPoint,4)'},
{'filter':
"""(eq,error,'"{"title": "ERROR",
"status": 500, "detail": "ERROR"}"')"""},
{'filter':
"""(in,changedInfo,'"{"vnfInstanceName": "test"}"')"""},
{'filter':
"""(neq,operationParams,'"{"terminationType": "FORCEFUL"}"')"""},