# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation
# All Rights Reserved.
#
#    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 oslo_utils import timeutils
from oslo_utils import uuidutils

from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import subscription_utils as subsc_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored import objects
from tacker.sol_refactored.objects.v2 import fields


LOG = logging.getLogger(__name__)  # not used at the moment


def get_lcmocc(context, lcmocc_id):
    lcmocc = objects.VnfLcmOpOccV2.get_by_id(context, lcmocc_id)
    if lcmocc is None:
        raise sol_ex.VnfLcmOpOccNotFound(lcmocc_id=lcmocc_id)
    return lcmocc


def get_lcmocc_all(context, marker=None):
    return objects.VnfLcmOpOccV2.get_all(context, marker)


def lcmocc_href(lcmocc_id, endpoint):
    return "{}/vnflcm/v2/vnf_lcm_op_occs/{}".format(endpoint, lcmocc_id)


def lcmocc_task_href(lcmocc_id, task, endpoint):
    return "{}/vnflcm/v2/vnf_lcm_op_occs/{}/{}".format(endpoint, lcmocc_id,
                                                       task)


def make_lcmocc_links(lcmocc, endpoint):
    links = {'self': {'href': lcmocc_href(lcmocc['id'], endpoint)}}
    links['vnfInstance'] = {
        'href': inst_utils.inst_href(lcmocc['vnfInstanceId'], endpoint)}
    links['retry'] = {
        'href': lcmocc_task_href(lcmocc['id'], 'retry', endpoint)}
    links['rollback'] = {
        'href': lcmocc_task_href(lcmocc['id'], 'rollback', endpoint)}
    links['fail'] = {'href': lcmocc_task_href(lcmocc['id'], 'fail', endpoint)}
    # TODO(oda-g): add when implemented
    # links.grant
    # links.cancel
    # links.vnfSnapshot

    return links


def make_lcmocc_notif_data(subsc, lcmocc, endpoint):
    notif_data = objects.VnfLcmOperationOccurrenceNotificationV2(
        id=uuidutils.generate_uuid(),
        notificationType="VnfLcmOperationOccurrenceNotification",
        subscriptionId=subsc.id,
        timeStamp=timeutils.utcnow(),
        operationState=lcmocc.operationState,
        vnfInstanceId=lcmocc.vnfInstanceId,
        operation=lcmocc.operation,
        isAutomaticInvocation=lcmocc.isAutomaticInvocation,
        verbosity=subsc.verbosity,
        vnfLcmOpOccId=lcmocc.id,
        _links=objects.LccnLinksV2(
            vnfInstance=objects.NotificationLink(
                href=inst_utils.inst_href(lcmocc.vnfInstanceId, endpoint)),
            subscription=objects.NotificationLink(
                href=subsc_utils.subsc_href(subsc.id, endpoint)),
            vnfLcmOpOcc=objects.NotificationLink(
                href=lcmocc_href(lcmocc.id, endpoint))
        )
    )

    if ((lcmocc.operation == fields.LcmOperationType.MODIFY_INFO and
         lcmocc.operationState == fields.LcmOperationStateType.PROCESSING) or
            lcmocc.operationState == fields.LcmOperationStateType.STARTING):
        notif_data.notificationStatus = 'START'
    else:
        notif_data.notificationStatus = 'RESULT'

    if lcmocc.operationState in [fields.LcmOperationStateType.FAILED_TEMP,
                                 fields.LcmOperationStateType.FAILED,
                                 fields.LcmOperationStateType.ROLLED_BACK]:
        # The existence of lcmocc.error is guaranteed.
        notif_data.error = lcmocc.error

    if notif_data.verbosity == fields.LcmOpOccNotificationVerbosityType.FULL:
        if lcmocc.obj_attr_is_set('resourceChanges'):
            attrs = ['affectedVnfcs',
                     'affectedVirtualLinks',
                     'affectedExtLinkPorts',
                     'affectedVirtualStorages']
            for attr in attrs:
                if lcmocc.resourceChanges.obj_attr_is_set(attr):
                    notif_data[attr] = lcmocc.resourceChanges[attr]
        attrs = ['changedInfo',
                 'changedExtConnectivity',
                 'modificationsTriggeredByVnfPkgChange']
        for attr in attrs:
            if lcmocc.obj_attr_is_set(attr):
                notif_data[attr] = lcmocc[attr]

    return notif_data


def _make_affected_vnfc(vnfc, change_type, strgs):
    affected_vnfc = objects.AffectedVnfcV2(
        id=vnfc.id,
        vduId=vnfc.vduId,
        changeType=change_type,
        computeResource=vnfc.computeResource
    )
    if vnfc.obj_attr_is_set('zoneId'):
        affected_vnfc.zoneId = vnfc.zoneId
    if vnfc.obj_attr_is_set('metadata'):
        affected_vnfc.metadata = vnfc.metadata
    if vnfc.obj_attr_is_set('vnfcCpInfo'):
        cp_ids = [cp.id for cp in vnfc.vnfcCpInfo]
        affected_vnfc.affectedVnfcCpIds = cp_ids
    if vnfc.obj_attr_is_set('storageResourceIds'):
        # NOTE: in case of heal, volume may not be deleted/re-created.
        if change_type == 'ADDED':
            str_ids = [str_id for str_id in vnfc.storageResourceIds
                       if str_id in strgs]
            if str_ids:
                affected_vnfc.addedStorageResourceIds = str_ids
        else:  # 'REMOVED'
            str_ids = [str_id for str_id in vnfc.storageResourceIds
                       if str_id in strgs]
            if str_ids:
                affected_vnfc.removedStorageResourceIds = str_ids

    return affected_vnfc


def _make_affected_vnfc_modified(vnfc, vnfc_saved):
    affected_vnfc = objects.AffectedVnfcV2(
        id=vnfc.id,
        vduId=vnfc.vduId,
        changeType='MODIFIED',
        computeResource=vnfc.computeResource
    )
    affected_vnfc.metadata = vnfc.metadata
    # NOTE: cps may not be affected.
    if vnfc.obj_attr_is_set('vnfcCpInfo'):
        cp_ids = [cp.id for cp in vnfc.vnfcCpInfo]
        affected_vnfc.affectedVnfcCpIds = cp_ids
    if vnfc.obj_attr_is_set('storageResourceIds'):
        added_str_ids = (set(vnfc.storageResourceIds) -
                         set(vnfc_saved.storageResourceIds))
        removed_str_ids = (set(vnfc_saved.storageResourceIds) -
                           set(vnfc.storageResourceIds))
        if added_str_ids:
            affected_vnfc.addedStorageResourceIds = list(added_str_ids)
        if removed_str_ids:
            affected_vnfc.removedStorageResourceIds = list(removed_str_ids)

    return affected_vnfc


def _make_affected_vl(vl, change_type):
    affected_vl = objects.AffectedVirtualLinkV2(
        id=vl.id,
        vnfVirtualLinkDescId=vl.vnfVirtualLinkDescId,
        changeType=change_type,
        networkResource=vl.networkResource
    )
    if vl.obj_attr_is_set('vnfLinkPorts'):
        affected_vl.vnfLinkPortIds = [port.id for port in vl.vnfLinkPorts]

    return affected_vl


def _make_affected_vls_link_port_change(vls_saved, vls, common_vls):
    affected_vls = []

    for vl_id in common_vls:
        old_ports = set()
        new_ports = set()
        for vl in vls_saved:
            if vl.id == vl_id:
                old_vl = vl
                if vl.obj_attr_is_set('vnfLinkPorts'):
                    old_ports = {port.id for port in vl.vnfLinkPorts}
        for vl in vls:
            if vl.id == vl_id:
                new_vl = vl
                if vl.obj_attr_is_set('vnfLinkPorts'):
                    new_ports = {port.id for port in vl.vnfLinkPorts}
        add_ports = new_ports - old_ports
        rm_ports = old_ports - new_ports
        # NOTE: Only for extManagedVirtualLink in case of heal
        # (SOL003 all=true) there may be both of add_ports and rm_ports.
        if add_ports:
            affected_vl = objects.AffectedVirtualLinkV2(
                id=new_vl.id,
                vnfVirtualLinkDescId=new_vl.vnfVirtualLinkDescId,
                changeType='LINK_PORT_ADDED',
                networkResource=new_vl.networkResource,
                vnfLinkPortIds=list(add_ports)
            )
            affected_vls.append(affected_vl)
        if rm_ports:
            affected_vl = objects.AffectedVirtualLinkV2(
                id=old_vl.id,
                vnfVirtualLinkDescId=old_vl.vnfVirtualLinkDescId,
                changeType='LINK_PORT_REMOVED',
                networkResource=old_vl.networkResource,
                vnfLinkPortIds=list(rm_ports)
            )
            affected_vls.append(affected_vl)

    return affected_vls


def _make_affected_strg(strg, change_type):
    return objects.AffectedVirtualStorageV2(
        id=strg.id,
        virtualStorageDescId=strg.virtualStorageDescId,
        changeType=change_type,
        storageResource=strg.storageResource
    )


def _make_affected_ext_link_ports(inst_info_saved, inst_info):
    affected_ext_link_ports = []

    ext_vl_ports_saved = set()
    ext_vl_ports = set()
    if inst_info_saved.obj_attr_is_set('extVirtualLinkInfo'):
        for ext_vl in inst_info_saved.extVirtualLinkInfo:
            if ext_vl.obj_attr_is_set('extLinkPorts'):
                ext_vl_ports_saved |= {port.id
                    for port in ext_vl.extLinkPorts}
    if inst_info.obj_attr_is_set('extVirtualLinkInfo'):
        for ext_vl in inst_info.extVirtualLinkInfo:
            if ext_vl.obj_attr_is_set('extLinkPorts'):
                ext_vl_ports |= {port.id
                    for port in ext_vl.extLinkPorts}
    add_ext_vl_ports = ext_vl_ports - ext_vl_ports_saved
    rm_ext_vl_ports = ext_vl_ports_saved - ext_vl_ports

    if add_ext_vl_ports:
        for ext_vl in inst_info.extVirtualLinkInfo:
            if not ext_vl.obj_attr_is_set('extLinkPorts'):
                continue
            affected_ext_link_ports += [
                objects.AffectedExtLinkPortV2(
                    id=port.id,
                    changeType='ADDED',
                    extCpInstanceId=port.cpInstanceId,
                    resourceHandle=port.resourceHandle
                )
                for port in ext_vl.extLinkPorts
                if port.id in add_ext_vl_ports
            ]
    if rm_ext_vl_ports:
        for ext_vl in inst_info_saved.extVirtualLinkInfo:
            if not ext_vl.obj_attr_is_set('extLinkPorts'):
                continue
            affected_ext_link_ports += [
                objects.AffectedExtLinkPortV2(
                    id=port.id,
                    changeType='REMOVED',
                    extCpInstanceId=port.cpInstanceId,
                    resourceHandle=port.resourceHandle
                )
                for port in ext_vl.extLinkPorts
                if port.id in rm_ext_vl_ports
            ]

    return affected_ext_link_ports


def _check_modification(inst_saved, inst, attr):
    if not inst.obj_attr_is_set(attr):
        return False
    if (not inst_saved.obj_attr_is_set(attr) or
            getattr(inst_saved, attr) != getattr(inst, attr)):
        return True
    return False


def _check_modification_inst(obj, inst_saved, inst):
    obj.vnfdId = inst.vnfdId
    if inst_saved.vnfProvider != inst.vnfProvider:
        obj.vnfProvider = inst.vnfProvider
    if inst_saved.vnfProductName != inst.vnfProductName:
        obj.vnfProductName = inst.vnfProductName
    if inst_saved.vnfSoftwareVersion != inst.vnfSoftwareVersion:
        obj.vnfSoftwareVersion = inst.vnfSoftwareVersion
    if inst_saved.vnfdVersion != inst.vnfdVersion:
        obj.vnfdVersion = inst.vnfdVersion


def _change_vnf_info(lcmocc, inst_saved, inst):
    vnf_info_modify = objects.VnfInfoModificationsV2()

    # vnfdid is required, don't check the existence of the value.
    if inst_saved.vnfdId != inst.vnfdId:
        _check_modification_inst(vnf_info_modify, inst_saved, inst)

    attrs = ['vnfInstanceName', 'vnfInstanceDescription',
             'vnfConfigurableProperties', 'metadata', 'extensions']
    for attr in attrs:
        if _check_modification(inst_saved, inst, attr):
            setattr(vnf_info_modify, attr, getattr(inst, attr))

    if (inst.obj_attr_is_set('vimConnectionInfo') and
            inst_saved.obj_attr_is_set('vimConnectionInfo')):
        inst_viminfo = inst.to_dict()["vimConnectionInfo"]
        inst_saved_viminfo = inst_saved.to_dict()["vimConnectionInfo"]
        if inst_viminfo != inst_saved_viminfo:
            vnf_info_modify.vimConnectionInfo = inst.vimConnectionInfo
    elif (inst.obj_attr_is_set('vimConnectionInfo') and
            not inst_saved.obj_attr_is_set('vimConnectionInfo')):
        vnf_info_modify.vimConnectionInfo = inst.vimConnectionInfo

    vnfc_info_mod = []
    vnfc_info = []
    if (inst.obj_attr_is_set('instantiatedVnfInfo') and
            inst.instantiatedVnfInfo.obj_attr_is_set('vnfcInfo')):
        vnfc_info = inst.instantiatedVnfInfo.vnfcInfo
    vnfc_info_saved = []
    if (inst_saved.obj_attr_is_set('instantiatedVnfInfo') and
            inst_saved.instantiatedVnfInfo.obj_attr_is_set('vnfcInfo')):
        vnfc_info_saved = inst_saved.instantiatedVnfInfo.vnfcInfo

    for vnfc in vnfc_info:
        prop_saved = {}
        for vnfc_saved in vnfc_info_saved:
            if vnfc.id == vnfc_saved.id:
                if vnfc_saved.obj_attr_is_set('vnfcConfigurableProperties'):
                    prop_saved = vnfc_saved.vnfcConfigurableProperties
                break
        prop = {}
        if vnfc.obj_attr_is_set('vnfcConfigurableProperties'):
            prop = vnfc.vnfcConfigurableProperties
        if prop != prop_saved:
            vnfc_info_mod.append(
                objects.VnfcInfoModificationsV2(
                    id=vnfc.id,
                    vnfcConfigurableProperties=prop
                )
            )
    if vnfc_info_mod:
        vnf_info_modify.vnfcInfoModifications = vnfc_info_mod

    lcmocc.changedInfo = vnf_info_modify


def _create_modifications_triggered_by_vnfpkg_change(lcmocc, inst_saved, inst):
    vnfpkg_modifications = objects.ModificationsTriggeredByVnfPkgChangeV2()
    _check_modification_inst(vnfpkg_modifications, inst_saved, inst)

    attrs = ['vnfConfigurableProperties', 'metadata', 'extensions']
    for attr in attrs:
        if _check_modification(inst_saved, inst, attr):
            setattr(vnfpkg_modifications, attr, getattr(inst, attr))

    lcmocc.modificationsTriggeredByVnfPkgChange = vnfpkg_modifications


def update_lcmocc(lcmocc, inst_saved, inst):
    # if operation is MODIFY_INFO, make changedInfo of lcmocc.
    # for other operations, make ResourceChanges of lcmocc
    # from instantiatedVnfInfo. In addition if operation is
    # CHANGE_EXT_CONN, make changedExtConnectivity of lcmocc.
    # NOTE: grant related info such as resourceDefinitionId, zoneId
    # and so on are not included in lcmocc since such info are not
    # included in instantiatedVnfInfo.

    if lcmocc.operation == fields.LcmOperationType.MODIFY_INFO:
        _change_vnf_info(lcmocc, inst_saved, inst)
        return

    if inst_saved.obj_attr_is_set('instantiatedVnfInfo'):
        inst_info_saved = inst_saved.instantiatedVnfInfo
    else:
        # dummy
        inst_info_saved = objects.VnfInstanceV2_InstantiatedVnfInfo()

    inst_info = inst.instantiatedVnfInfo

    # NOTE: objects may be re-created. so compare 'id' instead of object
    # itself.
    def _calc_diff(attr):
        # NOTE: instantiatedVnfInfo object is dict compat
        objs_saved = set()
        if inst_info_saved.obj_attr_is_set(attr):
            objs_saved = {obj.id for obj in inst_info_saved[attr]}
        objs = set()
        if inst_info.obj_attr_is_set(attr):
            objs = {obj.id for obj in inst_info[attr]}

        # return removed_objs, added_objs, common_objs
        return objs_saved - objs, objs - objs_saved, objs_saved & objs

    removed_strgs, added_strgs, _ = _calc_diff('virtualStorageResourceInfo')
    affected_strgs = []
    if removed_strgs:
        affected_strgs += [
            _make_affected_strg(strg, 'REMOVED')
            for strg in inst_info_saved.virtualStorageResourceInfo
            if strg.id in removed_strgs
        ]
    if added_strgs:
        affected_strgs += [_make_affected_strg(strg, 'ADDED')
                           for strg in inst_info.virtualStorageResourceInfo
                           if strg.id in added_strgs]

    removed_vnfcs, added_vnfcs, common_vnfcs = _calc_diff('vnfcResourceInfo')
    affected_vnfcs = []
    if removed_vnfcs:
        affected_vnfcs += [
            _make_affected_vnfc(vnfc, 'REMOVED', removed_strgs)
            for vnfc in inst_info_saved.vnfcResourceInfo
            if vnfc.id in removed_vnfcs
        ]
    if added_vnfcs:
        affected_vnfcs += [
            _make_affected_vnfc(vnfc, 'ADDED', added_strgs)
            for vnfc in inst_info.vnfcResourceInfo
            if vnfc.id in added_vnfcs
        ]
    if lcmocc.operation == fields.LcmOperationType.CHANGE_VNFPKG:
        def _get_vnfc(vnfc_id, inst_info):
            for vnfc in inst_info.vnfcResourceInfo:
                if vnfc.id == vnfc_id:
                    return vnfc

        for obj_id in common_vnfcs:
            vnfc = _get_vnfc(obj_id, inst_info)
            vnfc_saved = _get_vnfc(obj_id, inst_saved.instantiatedVnfInfo)
            if vnfc.metadata != vnfc_saved.metadata:
                affected_vnfcs.append(
                    _make_affected_vnfc_modified(vnfc, vnfc_saved))

        _create_modifications_triggered_by_vnfpkg_change(
            lcmocc, inst_saved, inst)

    removed_vls, added_vls, common_vls = _calc_diff(
        'vnfVirtualLinkResourceInfo')
    affected_vls = []
    if removed_vls:
        affected_vls += [_make_affected_vl(vl, 'REMOVED')
                         for vl in inst_info_saved.vnfVirtualLinkResourceInfo
                         if vl.id in removed_vls]
    if added_vls:
        affected_vls += [_make_affected_vl(vl, 'ADDED')
                         for vl in inst_info.vnfVirtualLinkResourceInfo
                         if vl.id in added_vls]
    if common_vls:
        affected_vls += _make_affected_vls_link_port_change(
            inst_info_saved.vnfVirtualLinkResourceInfo,
            inst_info.vnfVirtualLinkResourceInfo, common_vls)

    removed_mgd_vls, added_mgd_vls, common_mgd_vls = _calc_diff(
        'extManagedVirtualLinkInfo')
    if removed_mgd_vls:
        affected_vls += [_make_affected_vl(vl, 'LINK_PORT_REMOVED')
                         for vl in inst_info_saved.extManagedVirtualLinkInfo
                         if vl.id in removed_mgd_vls]
    if added_mgd_vls:
        affected_vls += [_make_affected_vl(vl, 'LINK_PORT_ADDED')
                         for vl in inst_info.extManagedVirtualLinkInfo
                         if vl.id in added_mgd_vls]
    if common_mgd_vls:
        affected_vls += _make_affected_vls_link_port_change(
            inst_info_saved.extManagedVirtualLinkInfo,
            inst_info.extManagedVirtualLinkInfo, common_mgd_vls)

    affected_ext_link_ports = _make_affected_ext_link_ports(
        inst_info_saved, inst_info)

    if (affected_vnfcs or affected_vls or affected_strgs or
            affected_ext_link_ports):
        change_info = objects.VnfLcmOpOccV2_ResourceChanges()
        if affected_vnfcs:
            change_info.affectedVnfcs = affected_vnfcs
        if affected_vls:
            change_info.affectedVirtualLinks = affected_vls
        if affected_strgs:
            change_info.affectedVirtualStorages = affected_strgs
        if affected_ext_link_ports:
            change_info.affectedExtLinkPorts = affected_ext_link_ports
        lcmocc.resourceChanges = change_info

    if lcmocc.operation == fields.LcmOperationType.CHANGE_EXT_CONN:
        _, added_ext_vls, common_ext_vls = _calc_diff('extVirtualLinkInfo')

        def _get_ext_vl(vl_id, vl_array):
            for vl in vl_array:
                if vl.id == vl_id:
                    return vl

        chg_ext_conn = [_get_ext_vl(ext_vl_id, inst_info.extVirtualLinkInfo)
                        for ext_vl_id in added_ext_vls]

        for ext_vl_id in common_ext_vls:
            ext_vl = _get_ext_vl(ext_vl_id, inst_info.extVirtualLinkInfo)
            ext_vl_saved = _get_ext_vl(ext_vl_id,
                                       inst_info_saved.extVirtualLinkInfo)
            cp_data = []
            if ext_vl.obj_attr_is_set('currentVnfExtCpData'):
                cp_data = sorted(ext_vl.to_dict()['currentVnfExtCpData'],
                                 key=lambda x: x['cpdId'])
            cp_data_saved = []
            if ext_vl_saved.obj_attr_is_set('currentVnfExtCpData'):
                cp_data_saved = sorted(
                    ext_vl_saved.to_dict()['currentVnfExtCpData'],
                    key=lambda x: x['cpdId'])
            if cp_data != cp_data_saved:
                chg_ext_conn.append(ext_vl)
                continue

            # NOTE: For ports created by the heat, the id is not changed if
            # its resourceId is not changed. But for ports given outside the
            # heat, the resourceId may be changed without changing the id,
            # so it is a policy to set changedExtConnectivity when there is
            # a change in "id" or "resourceHandle.resourceId".
            port_ids = set()
            if ext_vl.obj_attr_is_set('extLinkPorts'):
                port_ids = {(port.id, port.resourceHandle.resourceId)
                            for port in ext_vl.extLinkPorts}
            port_ids_saved = set()
            if ext_vl_saved.obj_attr_is_set('extLinkPorts'):
                port_ids_saved = {(port.id, port.resourceHandle.resourceId)
                                  for port in ext_vl_saved.extLinkPorts}
            if port_ids != port_ids_saved:
                chg_ext_conn.append(ext_vl)

        if chg_ext_conn:
            lcmocc.changedExtConnectivity = chg_ext_conn


def get_inst_lcmocc(context, inst):
    lcmoccs = objects.VnfLcmOpOccV2.get_by_filter(
        context, vnfInstanceId=inst.id,
        operationState=fields.LcmOperationStateType.COMPLETED,
        operation=fields.LcmOperationType.INSTANTIATE)
    inst_lcmocc = [inst_lcmocc for inst_lcmocc in lcmoccs
                   if inst_lcmocc.startTime ==
                   max([lcmocc.startTime for lcmocc in lcmoccs])][0]
    return inst_lcmocc


def get_grant_req_and_grant(context, lcmocc):
    if lcmocc.operation == fields.LcmOperationType.MODIFY_INFO:
        return None, None

    grant_reqs = objects.GrantRequestV1.get_by_filter(context,
                                                      vnfLcmOpOccId=lcmocc.id)
    grant = objects.GrantV1.get_by_id(context, lcmocc.grantId)
    if not grant_reqs or grant is None:
        raise sol_ex.GrantRequestOrGrantNotFound(lcmocc_id=lcmocc.id)

    # len(grant_reqs) == 1 because vnfLcmOpOccId is primary key.
    return grant_reqs[0], grant


def is_lcmocc_failure_status(context, inst_id):
    inst_lcmoccs = objects.VnfLcmOpOccV2.get_by_filter(
        context, vnfInstanceId=inst_id)
    failed_temp_lcmoccs = [
        lcmocc for lcmocc in inst_lcmoccs
        if lcmocc.operationState == fields.LcmOperationStateType.FAILED_TEMP]
    failed_lcmocc = [
        latest_lcmocc for latest_lcmocc in inst_lcmoccs
        if latest_lcmocc.startTime == max(
            [lcmocc.startTime for lcmocc in inst_lcmoccs])]

    if failed_temp_lcmoccs or (failed_lcmocc[0].operationState ==
                               fields.LcmOperationStateType.FAILED):
        return True

    return False


def check_lcmocc_in_progress(context, inst_id):
    # if the controller or conductor executes an operation for the vnf
    # instance (i.e. operationState is ...ING), other operation for
    # the same vnf instance is excluded by the coordinator.
    # check here is existence of lcmocc for the vnf instance with
    # FAILED_TEMP operationState.
    lcmoccs = objects.VnfLcmOpOccV2.get_by_filter(
        context, vnfInstanceId=inst_id,
        operationState=fields.LcmOperationStateType.FAILED_TEMP)
    if lcmoccs:
        raise sol_ex.OtherOperationInProgress(inst_id=inst_id)


def update_lcmocc_status(lcmocc, op_state):
    lcmocc.operationState = op_state
    lcmocc.stateEnteredTime = timeutils.utcnow()


def new_lcmocc(inst_id, operation, req_body,
               op_state=fields.LcmOperationStateType.STARTING,
               auto_invocation=False):
    now = timeutils.utcnow()
    lcmocc = objects.VnfLcmOpOccV2(
        id=uuidutils.generate_uuid(),
        operationState=op_state,
        stateEnteredTime=now,
        startTime=now,
        vnfInstanceId=inst_id,
        operation=operation,
        isAutomaticInvocation=auto_invocation,
        isCancelPending=False,
        operationParams=req_body)

    return lcmocc
