#
# Copyright (c) 2019 Juniper Networks, Inc. All rights reserved.
#


from builtins import range
from builtins import str
import os

from cfgm_common import _obj_serializer_all
from cfgm_common import jsonutils as json
from cfgm_common.exceptions import NoIdError
from cfgm_common.exceptions import ResourceExistsError
from cfgm_common.utils import _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX
from cfgm_common.utils import _DEFAULT_ZK_FABRIC_SP_PATH_PREFIX
from cfgm_common.zkclient import ZookeeperLock
from netaddr import AddrFormatError
from netaddr import IPAddress
from vnc_api.gen.resource_common import VirtualMachineInterface
from vnc_api.gen.resource_common import VirtualPortGroup
from vnc_api.gen.resource_xsd import MacAddressesType
from vnc_api.gen.resource_xsd import PolicyBasedForwardingRuleType

from vnc_cfg_api_server.context import get_context
from vnc_cfg_api_server.resources._resource_base import ResourceMixin


class VirtualMachineInterfaceServer(ResourceMixin, VirtualMachineInterface):
    portbindings = {}
    portbindings['VIF_TYPE_VROUTER'] = 'vrouter'
    portbindings['VIF_TYPE_HW_VEB'] = 'hw_veb'
    portbindings['VNIC_TYPE_NORMAL'] = 'normal'
    portbindings['VNIC_TYPE_DIRECT'] = 'direct'
    portbindings['VNIC_TYPE_BAREMETAL'] = 'baremetal'
    portbindings['VNIC_TYPE_VIRTIO_FORWARDER'] = 'virtio-forwarder'
    portbindings['PORT_FILTER'] = True
    portbindings['VIF_TYPE_VHOST_USER'] = 'vhostuser'
    portbindings['VHOST_USER_MODE'] = 'vhostuser_mode'
    portbindings['VHOST_USER_MODE_SERVER'] = 'server'
    portbindings['VHOST_USER_MODE_CLIENT'] = 'client'
    portbindings['VHOST_USER_SOCKET'] = 'vhostuser_socket'
    portbindings['VHOST_USER_VROUTER_PLUG'] = 'vhostuser_vrouter_plug'

    vhostuser_sockets_dir = '/var/run/vrouter/'
    NIC_NAME_LEN = 14

    @staticmethod
    def _kvp_to_dict(kvps):
        return dict((kvp['key'], kvp['value']) for kvp in kvps)

    @classmethod
    def _check_vrouter_link(cls, vmi_data, kvp_dict, obj_dict, db_conn):
        api_server = db_conn.get_api_server()

        host_id = kvp_dict.get('host_id')
        if not host_id:
            return

        vm_refs = vmi_data.get('virtual_machine_refs')
        if not vm_refs:
            vm_refs = obj_dict.get('virtual_machine_refs')
            if not vm_refs:
                return

        vrouter_fq_name = ['default-global-system-config', host_id]
        try:
            vrouter_id = db_conn.fq_name_to_uuid('virtual_router',
                                                 vrouter_fq_name)
        except NoIdError:
            return

        # if virtual_machine_refs is an empty list delete vrouter link
        if ('virtual_machine_refs' in obj_dict and not
                obj_dict['virtual_machine_refs']):
            api_server.internal_request_ref_update(
                'virtual-router',
                vrouter_id,
                'DELETE',
                'virtual_machine',
                vm_refs[0]['uuid'])
            return

        api_server.internal_request_ref_update(
            'virtual-router',
            vrouter_id,
            'ADD',
            'virtual-machine',
            vm_refs[0]['uuid'])

    @classmethod
    def _check_bridge_domain_vmi_association(
            cls, obj_dict, db_dict, db_conn, vn_uuid, create):
        if ('virtual_network_refs' not in obj_dict and
                'bridge_domain_refs' not in obj_dict):
            return True, ''

        vn_fq_name = []
        if vn_uuid:
            vn_fq_name = db_conn.uuid_to_fq_name(vn_uuid)

        bridge_domain_links = {}
        bd_refs = obj_dict.get('bridge_domain_refs') or []

        for bd in bd_refs:
            bd_fq_name = bd['to']
            if bd_fq_name[0:3] != vn_fq_name:
                msg = ("Virtual machine interface(%s) can only refer to "
                       "bridge domain belonging to virtual network(%s)" %
                       (obj_dict['uuid'], vn_uuid))
                return False, msg
            bd_uuid = bd.get('uuid')
            if not bd_uuid:
                bd_uuid = db_conn.fq_name_to_uuid('bridge_domain', bd_fq_name)

            bdmt = bd['attr']
            vlan_tag = bdmt['vlan_tag']
            if vlan_tag in bridge_domain_links:
                msg = ("Virtual machine interface(%s) already refers to "
                       "bridge domain(%s) for vlan tag %d" %
                       (obj_dict['uuid'], bd_uuid, vlan_tag))
                return False, msg

            bridge_domain_links[vlan_tag] = bd_uuid

        # disallow final state where bd exists but vn doesn't
        if 'virtual_network_refs' in obj_dict:
            vn_refs = obj_dict['virtual_network_refs']
        else:
            vn_refs = (db_dict or {}).get('virtual_network_refs')

        if not vn_refs:
            if 'bridge_domain_refs' not in obj_dict:
                bd_refs = db_dict.get('bridge_domain_refs')
            if bd_refs:
                msg = ("Virtual network can not be removed on virtual machine "
                       "interface(%s) while it refers a bridge domain" %
                       (obj_dict['uuid']))
                return False, msg

        return True, ''

    @classmethod
    def _check_port_security_and_address_pairs(cls, obj_dict, db_dict=None):
        if ('port_security_enabled' not in obj_dict and
                'virtual_machine_interface_allowed_address_pairs' not in
                obj_dict):
            return True, ""

        if not db_dict:
            db_dict = {}
        if 'port_security_enabled' in obj_dict:
            port_security = obj_dict.get('port_security_enabled', True)
        else:
            port_security = db_dict.get('port_security_enabled', True)

        if 'virtual_machine_interface_allowed_address_pairs' in obj_dict:
            address_pairs = obj_dict.get(
                'virtual_machine_interface_allowed_address_pairs')
        else:
            address_pairs = db_dict.get(
                'virtual_machine_interface_allowed_address_pairs')

        # Validate the format of allowed_address_pair
        if address_pairs:
            for aap in address_pairs.get('allowed_address_pair', {}):
                try:
                    IPAddress(aap.get('ip').get('ip_prefix'))
                except AddrFormatError as e:
                    return (False, (400, str(e)))

        if (not port_security and address_pairs and
                address_pairs.get('allowed_address_pair')):
            msg = "Allowed address pairs are not allowed when port "\
                  "security is disabled"
            return False, (400, msg)

        return True, ""

    @classmethod
    def _check_service_health_check_type(cls, obj_dict, db_dict, db_conn):
        service_health_check_refs = obj_dict.get('service_health_check_refs')
        if not service_health_check_refs:
            return True, ''

        if obj_dict and obj_dict.get('port_tuple_refs'):
            return True, ''

        if db_dict and db_dict.get('port_tuple_refs'):
            return True, ''

        for shc in service_health_check_refs or []:
            if 'uuid' in shc:
                shc_uuid = shc['uuid']
            else:
                shc_fq_name = shc['to']
                shc_uuid = db_conn.fq_name_to_uuid(
                    'service_health_check', shc_fq_name)
            ok, result = cls.dbe_read(
                db_conn,
                'service_health_check',
                shc_uuid,
                obj_fields=['service_health_check_properties'])
            if not ok:
                return ok, result

            shc_type = result['service_health_check_properties'
                              ]['health_check_type']
            if shc_type != 'link-local':
                msg = ("Virtual machine interface(%s) of non service vm can "
                       "only refer link-local type service health check" %
                       obj_dict['uuid'])
                return False, (400, msg)

        return True, ''

    @classmethod
    def _is_port_bound(cls, obj_dict, new_kvp_dict):
        """Check whatever port is bound.

        For any NON 'baremetal port' we assume port is bound when it is linked
        to either VM or Vrouter.
        For any 'baremetal' port we assume it is bound when it has set:
        * binding:local_link_information
        * binding:host_id

        :param obj_dict: Current port dict to check
        :param new_kvp_dict: KVP dict of port update.
        :returns: True if port is bound, False otherwise.
        """
        bindings = obj_dict['virtual_machine_interface_bindings']
        kvps = bindings['key_value_pair']
        kvp_dict = cls._kvp_to_dict(kvps)
        old_vnic_type = kvp_dict.get('vnic_type')
        new_vnic_type = new_kvp_dict.get('vnic_type')

        if new_vnic_type == cls.portbindings['VNIC_TYPE_BAREMETAL']:
            return False

        if old_vnic_type == cls.portbindings['VNIC_TYPE_BAREMETAL']:
            if (kvp_dict.get('profile', {}).get('local_link_information') and
                    kvp_dict.get('host_id')):
                return True
        else:
            return (obj_dict.get('logical_router_back_refs') or
                    obj_dict.get('virtual_machine_refs'))

    @classmethod
    def _get_port_vhostuser_socket_name(cls, id=None):
        name = 'tap' + id
        name = name[:cls.NIC_NAME_LEN]
        return cls.vhostuser_sockets_dir + 'uvh_vif_' + name

    @classmethod
    def _is_dpdk_enabled(cls, obj_dict, db_conn, host_id=None):
        if host_id is None or not host_id:
            if obj_dict.get('virtual_machine_interface_bindings'):
                bindings = obj_dict['virtual_machine_interface_bindings']
                kvps = bindings['key_value_pair']
                kvp_dict = cls._kvp_to_dict(kvps)
                host_id = kvp_dict.get('host_id')
                if not host_id or host_id == 'null':
                    return True, False
            else:
                return True, False

        vrouter = None
        vrouter_fq_name = ['default-global-system-config', host_id]
        fields = ['uuid', 'fq_name', 'virtual_router_dpdk_enabled']
        ok, result = cls.server.get_resource_class('virtual_router').locate(
            vrouter_fq_name, create_it=False, fields=fields)
        if not ok and result[0] == 404:
            ok, result, _ = db_conn.dbe_list('virtual_router',
                                             field_names=fields)
            if not ok:
                return False, result
            vrs = result
            for vr in vrs:
                fullname = vr['fq_name'][-1].split('.')
                for hname in fullname:
                    if host_id.partition('.')[0] == hname:
                        vrouter = vr
                        break
                if vr == vrouter:
                    break
            if not vrouter:
                return True, result
        elif not ok:
            return False, result
        else:
            vrouter = result

        return True, vrouter.get('virtual_router_dpdk_enabled', False)

    @classmethod
    def _kvps_update(cls, kvps, kvp):
        key = kvp['key']
        value = kvp['value']
        kvp_dict = cls._kvp_to_dict(kvps)
        if key not in list(kvp_dict.keys()):
            if value:
                kvps.append(kvp)
        else:
            for i in range(len(kvps)):
                kvps_dict = dict(kvps[i])
                if key == kvps_dict.get('key'):
                    if value:
                        kvps[i]['value'] = value
                        break
                    else:
                        del kvps[i]
                        break

    @classmethod
    def _kvps_prop_update(cls, obj_dict, kvps, prop_collection_updates,
                          vif_type, vif_details, prop_set):
        if 'virtual_machine_interface_bindings' in obj_dict:
            cls._kvps_update(kvps, vif_type)
            cls._kvps_update(kvps, vif_details)
        else:
            if prop_set:
                vif_type_prop = {
                    'field': 'virtual_machine_interface_bindings',
                    'operation': 'set',
                    'value': vif_type,
                    'position': 'vif_type',
                }
                vif_details_prop = {
                    'field': 'virtual_machine_interface_bindings',
                    'operation': 'set',
                    'value': vif_details,
                    'position': 'vif_details',
                }
            else:
                vif_type_prop = {
                    'field': 'virtual_machine_interface_bindings',
                    'operation': 'delete',
                    'value': vif_type,
                    'position': 'vif_type',
                }
                vif_details_prop = {
                    'field': 'virtual_machine_interface_bindings',
                    'operation': 'delete',
                    'value': vif_details,
                    'position': 'vif_details',
                }
            prop_collection_updates.append(vif_details_prop)
            prop_collection_updates.append(vif_type_prop)

    @classmethod
    def add_vpg_ref(cls, obj_dict, vpg_uuid, vlan_id, untagged, db_conn):
        vpg_ref = {'to': db_conn.uuid_to_fq_name(
            vpg_uuid), 'uuid': vpg_uuid}
        if untagged:
            vpg_ref.update({'attr': {'native_vlan_tag': vlan_id}})
        else:
            vpg_ref.update({'attr': {'vlan_tag': vlan_id}})
        obj_dict['virtual_port_group_refs'] = [vpg_ref]
        return obj_dict

    @classmethod
    def pre_dbe_create(cls, tenant_name, obj_dict, db_conn):
        vn_dict = obj_dict['virtual_network_refs'][0]
        vn_uuid = vn_dict.get('uuid')
        if not vn_uuid:
            vn_fq_name = vn_dict.get('to')
            if not vn_fq_name:
                msg = ("Virtual Machine Interface must have valide Virtual "
                       "Network reference")
                return False, (400, msg)
            vn_uuid = db_conn.fq_name_to_uuid('virtual_network', vn_fq_name)

        ok, result = cls.dbe_read(
            db_conn,
            'virtual_network',
            vn_uuid,
            obj_fields=['parent_uuid', 'provider_properties'])
        if not ok:
            return ok, result
        vn_dict = result

        vlan_tag = (obj_dict.get('virtual_machine_interface_properties') or {}
                    ).get('sub_interface_vlan_tag') or 0
        if vlan_tag < 0 or vlan_tag > 4094:
            return False, (400, "Invalid sub-interface VLAN tag ID: %s" %
                           vlan_tag)
        if vlan_tag and 'virtual_machine_interface_refs' in obj_dict:
            primary_vmi_ref = obj_dict['virtual_machine_interface_refs']
            ok, primary_vmi = cls.dbe_read(
                db_conn,
                'virtual_machine_interface',
                primary_vmi_ref[0]['uuid'],
                obj_fields=['virtual_machine_interface_refs',
                            'virtual_machine_interface_properties'])
            if not ok:
                return ok, primary_vmi

            primary_vmi_vlan_tag = primary_vmi.get(
                'virtual_machine_interface_properties', {}).get(
                    'sub_interface_vlan_tag')
            if primary_vmi_vlan_tag:
                msg = ("sub interface can't have another sub interface as "
                       "it's primary port")
                return False, (400, msg)

            sub_vmi_refs = primary_vmi.get('virtual_machine_interface_refs',
                                           {})
            sub_vmi_uuids = [ref['uuid'] for ref in sub_vmi_refs]

            if sub_vmi_uuids:
                ok, sub_vmis, _ = db_conn.dbe_list(
                    'virtual_machine_interface', obj_uuids=sub_vmi_uuids,
                    field_names=['virtual_machine_interface_properties'])
                if not ok:
                    return ok, sub_vmis

                sub_vmi_vlan_tags = [((vmi.get(
                    'virtual_machine_interface_properties') or {}).get(
                        'sub_interface_vlan_tag')) for vmi in sub_vmis]
                if vlan_tag in sub_vmi_vlan_tags:
                    msg = "Two sub interfaces under same primary port "\
                          "can't have same Vlan tag"
                    return (False, (400, msg))

        ok, error = cls._check_bridge_domain_vmi_association(
            obj_dict, None, db_conn, vn_uuid, True)
        if not ok:
            return (False, (400, error))

        inmac = None
        if 'virtual_machine_interface_mac_addresses' in obj_dict:
            mc = obj_dict['virtual_machine_interface_mac_addresses']
            if 'mac_address' in mc and mc['mac_address']:
                inmac = [m.replace("-", ":") for m in mc['mac_address']]
        if inmac is not None:
            mac_addrs_obj = MacAddressesType(inmac)
        else:
            mac_addr = cls.addr_mgmt.mac_alloc(obj_dict)
            mac_addrs_obj = MacAddressesType([mac_addr])
            # Handling CEM-21992 START
            if 'virtual_machine_interface_bindings' in obj_dict:
                bindings = obj_dict['virtual_machine_interface_bindings']
                kvps = bindings['key_value_pair']
                kvp_dict = cls._kvp_to_dict(kvps)
                """
                VMI create workflow is different for Ironic user and UI user.
                1. Ironic User doesn't send 'binding:profile' info during VMI
                   CREATE. VMI would be created with zero mac_address.
                2. UI user sends the 'binding:profile' info during VMI CREATE.
                   VMI would be created with mac updated from api_server.
                Fix for CEM-22518
                """
                profile_str = kvp_dict.get('profile')
                profile_dict = {}
                if profile_str:
                    profile_dict = json.loads(kvp_dict.get('profile'))
                vnic_type = kvp_dict.get('vnic_type')
                if vnic_type == 'baremetal' and \
                        profile_dict.get('local_link_information') is None:
                    # During the port-create, use all Zero Mac address as
                    # VMI's MAC
                    mac_addrs_obj = MacAddressesType([u'00:00:00:00:00:00'])
                    # Handling CEM-21992 END
        mac_addrs_json = json.dumps(
            mac_addrs_obj,
            default=lambda o: dict((k, v)
                                   for k, v in o.__dict__.items()))
        mac_addrs_dict = json.loads(mac_addrs_json)
        obj_dict['virtual_machine_interface_mac_addresses'] = mac_addrs_dict

        kvps = []
        if 'virtual_machine_interface_bindings' in obj_dict:
            bindings = obj_dict['virtual_machine_interface_bindings']
            kvps = bindings['key_value_pair']
            kvp_dict = cls._kvp_to_dict(kvps)

            if (kvp_dict.get('vnic_type') ==
                    cls.portbindings['VNIC_TYPE_DIRECT'] or
                    kvp_dict.get('vnic_type') ==
                    cls.portbindings['VNIC_TYPE_VIRTIO_FORWARDER']):
                # If the segmentation_id in provider_properties is set, then
                # a hw_veb VIF is requested.
                if ('provider_properties' in vn_dict and
                    vn_dict['provider_properties'] is not None and
                        'segmentation_id' in vn_dict['provider_properties']):
                    kvp_dict['vif_type'] = cls.portbindings['VIF_TYPE_HW_VEB']
                    vif_type = {
                        'key': 'vif_type',
                        'value': cls.portbindings['VIF_TYPE_HW_VEB'],
                    }
                    vlan = vn_dict['provider_properties']['segmentation_id']
                    vif_params = {
                        'port_filter': cls.portbindings['PORT_FILTER'],
                        'vlan': str(vlan),
                    }
                    vif_details = {
                        'key': 'vif_details',
                        'value': json.dumps(vif_params),
                    }
                    kvps.append(vif_details)
                    kvps.append(vif_type)
                else:
                    # An offloaded port is requested
                    vnic_type = {
                        'key': 'vnic_type',
                        'value': kvp_dict.get('vnic_type'),
                    }
                    if (kvp_dict.get('vnic_type') ==
                            cls.portbindings['VNIC_TYPE_VIRTIO_FORWARDER']):
                        vif_params = {
                            cls.portbindings['VHOST_USER_MODE']:
                            cls.portbindings['VHOST_USER_MODE_SERVER'],
                            cls.portbindings['VHOST_USER_SOCKET']:
                            cls._get_port_vhostuser_socket_name(
                                obj_dict['uuid'])
                        }
                        vif_details = {
                            'key': 'vif_details',
                            'value': json.dumps(vif_params),
                        }
                        kvps.append(vif_details)
                    kvps.append(vnic_type)

            if 'vif_type' not in kvp_dict:
                vif_type = {'key': 'vif_type',
                            'value': cls.portbindings['VIF_TYPE_VROUTER']}
                kvps.append(vif_type)

            if 'vnic_type' not in kvp_dict:
                vnic_type = {'key': 'vnic_type',
                             'value': cls.portbindings['VNIC_TYPE_NORMAL']}
                kvps.append(vnic_type)

            kvp_dict = cls._kvp_to_dict(kvps)
            if (kvp_dict.get('vnic_type') ==
                    cls.portbindings['VNIC_TYPE_NORMAL']):
                (ok, result) = cls._is_dpdk_enabled(obj_dict, db_conn)
                if not ok:
                    return ok, result
                elif result:
                    vif_type = {
                        'key': 'vif_type',
                        'value': cls.portbindings['VIF_TYPE_VHOST_USER'],
                    }
                    vif_params = {
                        cls.portbindings['VHOST_USER_MODE']:
                        cls.portbindings['VHOST_USER_MODE_SERVER'],
                        cls.portbindings['VHOST_USER_SOCKET']:
                        cls._get_port_vhostuser_socket_name(obj_dict['uuid']),
                        cls.portbindings['VHOST_USER_VROUTER_PLUG']: True
                    }
                    vif_details = {
                        'key': 'vif_details',
                        'value': json.dumps(vif_params),
                    }
                    cls._kvps_update(kvps, vif_type)
                    cls._kvps_update(kvps, vif_details)
                else:
                    vif_type = {'key': 'vif_type', 'value': None}
                    cls._kvps_update(kvps, vif_type)

        (ok, result) = cls._check_port_security_and_address_pairs(obj_dict)

        if not ok:
            return ok, result

        (ok, result) = cls._check_service_health_check_type(obj_dict, None,
                                                            db_conn)
        if not ok:
            return ok, result

        # Manage baremetal provisioning here
        if kvps:
            kvp_dict = cls._kvp_to_dict(kvps)
            vnic_type = kvp_dict.get('vnic_type')
            vpg_name = kvp_dict.get('vpg')
            ok, (vlan_id, is_untagged_vlan, links) = cls.get_vlan_phy_links(
                kvps, obj_dict)
            if not ok:
                return False, (400, "Cannot get vlan or phy_links")

            if vnic_type == 'baremetal':
                # Use cases:
                # 1. UI user can attach PI to VPG before VMI creation
                #    - If payload doesn't have link_local_info then retrieve it
                #      from DB and call _manage_vpg_association
                # 2. UI user sends PI during VMI creation
                #    - call _manage_vpg_association
                # 3. Ironic user doesn't have PI info at the time of VMI create
                #    - PI info won't be present in the DB or payload
                #    - Do not call _manage_vpg_association
                # 4. Non-Ironic or Non-UI/Api-client user can send VMI creation
                #    a. Without attaching PI info in the payload AND
                #    b. Without attaching PI to VPG before VMI creation
                if kvp_dict.get('profile'):
                    # Process only if port profile exists and physical links
                    # specified
                    phy_links = json.loads(kvp_dict.get('profile'))
                    if phy_links and phy_links.get('local_link_information'):
                        links = phy_links['local_link_information']
                # manage_vpg function needs to be called regardless of
                # phy_links
                vpg_refs = obj_dict.get('virtual_port_group_refs') or None

                # For Ironic user with OpenStack Ussuri release,
                # During port create, there won't be vpg_refs or binding
                # profile. _manage_vpg_association will fail further in
                # fabric validations. If no 'vpg' key in bindings, vpg object
                # cannot be retrieved.
                # Skip invoking _manage_vpg_association in all the above cases
                # CEM-21992
                if ((not vpg_name) and (not kvp_dict.get('profile') and
                   (not vpg_refs))):
                    return True, ""
                vpg_uuid, ret_dict, zk_update_kwargs = \
                    cls._manage_vpg_association(
                        obj_dict['uuid'], cls.server, db_conn, links, vpg_refs,
                        db_vlan_id=None, vpg_name=vpg_name, vn_uuid=vn_uuid,
                        vlan_id=vlan_id, is_untagged_vlan=is_untagged_vlan,
                        db_is_untagged_vlan=None)
                if not vpg_uuid:
                    return vpg_uuid, ret_dict
                obj_dict['port_virtual_port_group_id'] = vpg_uuid
                obj_dict.update(ret_dict)

                # Add reference from this VMI to VPG
                obj_dict = cls.add_vpg_ref(
                    obj_dict, vpg_uuid, vlan_id, is_untagged_vlan, db_conn)

        return True, ""

    @classmethod
    def post_dbe_create(cls, tenant_name, obj_dict, db_conn):
        api_server = db_conn.get_api_server()

        # add a ref from VPG to this VMI
        vpg_uuid = obj_dict.get('port_virtual_port_group_id')
        if vpg_uuid:
            api_server.internal_request_ref_update(
                'virtual-port-group',
                vpg_uuid,
                'ADD',
                'virtual-machine-interface',
                obj_dict['uuid'],
                relax_ref_for_delete=True)

        # Create ref to native/vn-default routing instance
        vn_refs = obj_dict.get('virtual_network_refs')
        if not vn_refs:
            return True, ''

        vn_fq_name = vn_refs[0].get('to')
        if not vn_fq_name:
            vn_uuid = vn_refs[0]['uuid']
            vn_fq_name = db_conn.uuid_to_fq_name(vn_uuid)

        ri_fq_name = vn_fq_name[:]
        ri_fq_name.append(vn_fq_name[-1])
        ri_uuid = db_conn.fq_name_to_uuid('routing_instance', ri_fq_name)

        attr = PolicyBasedForwardingRuleType(direction="both")
        attr_as_dict = attr.__dict__
        api_server.internal_request_ref_update(
            'virtual-machine-interface', obj_dict['uuid'], 'ADD',
            'routing-instance', ri_uuid, ri_fq_name,
            attr=attr_as_dict)

        return True, ''

    @classmethod
    def pre_dbe_update(cls, id, fq_name, obj_dict, db_conn,
                       prop_collection_updates=None, **kwargs):
        zk_update_kwargs = {}
        ok, read_result = cls.dbe_read(db_conn, 'virtual_machine_interface',
                                       id)
        if not ok:
            return ok, read_result, zk_update_kwargs

        vn_uuid = None
        if 'virtual_network_refs' in read_result:
            vn_uuid = read_result['virtual_network_refs'][0].get('uuid')
        ok, error = cls._check_bridge_domain_vmi_association(
            obj_dict, read_result, db_conn, vn_uuid, False)
        if not ok:
            return False, (400, error), zk_update_kwargs

        # check if the vmi is a internal interface of a logical
        # router
        if (read_result.get('logical_router_back_refs') and
                obj_dict.get('virtual_machine_refs')):
            return (False,
                    (400, 'Logical router interface cannot be used by VM'),
                    zk_update_kwargs)
        # check if vmi is going to point to vm and if its using
        # gateway address in iip, disallow
        iip_class = cls.server.get_resource_class('instance_ip')
        for iip_ref in read_result.get('instance_ip_back_refs') or []:
            if (obj_dict.get('virtual_machine_refs') and
                    iip_class.is_gateway_ip(db_conn, iip_ref['uuid'])):
                return (False,
                        (400, 'Gateway IP cannot be used by VM port'),
                        zk_update_kwargs)

        vpg = None
        if read_result.get('virtual_port_group_back_refs'):
            vpg_refs = read_result.get('virtual_port_group_back_refs')
            vpg_id = vpg_refs[0].get('uuid')
            ok, vpg = cls.dbe_read(
                db_conn, 'virtual_port_group',
                vpg_id, obj_fields=['virtual_port_group_trunk_port_id'])
            if not ok:
                return ok, vpg, zk_update_kwargs

        if ('virtual_machine_interface_refs' in obj_dict and
                'virtual_machine_interface_refs' in read_result):
            for ref in read_result['virtual_machine_interface_refs']:
                if (ref not in obj_dict['virtual_machine_interface_refs'] and
                        vpg and
                        vpg.get('virtual_port_group_trunk_port_id') != id):
                    # Dont allow remove of vmi ref during update if it's
                    # a sub port
                    msg = "VMI ref delete not allowed during update"
                    return (False, (409, msg), zk_update_kwargs)

        bindings = read_result.get('virtual_machine_interface_bindings') or {}
        kvps = bindings.get('key_value_pair') or []
        kvp_dict = cls._kvp_to_dict(kvps)
        old_vnic_type = kvp_dict.get('vnic_type',
                                     cls.portbindings['VNIC_TYPE_NORMAL'])

        bindings = obj_dict.get('virtual_machine_interface_bindings') or {}
        kvps = bindings.get('key_value_pair') or []

        vmib = False
        for oper_param in prop_collection_updates or []:
            if (oper_param['field'] == 'virtual_machine_interface_bindings' and
                    oper_param['operation'] == 'set'):
                vmib = True
                kvps.append(oper_param['value'])

        old_vlan = (read_result.get('virtual_machine_interface_properties') or
                    {}).get('sub_interface_vlan_tag') or 0
        new_vlan = old_vlan
        if 'virtual_machine_interface_properties' in obj_dict:
            new_vlan = (obj_dict['virtual_machine_interface_properties'] or
                        {}).get('sub_interface_vlan_tag') or 0
            # vRouter has limitations for VLAN tag update.
            # But, In case of neutron trunk port, subports are created first
            # and then associated a VLAN tag
            # To take care of limitations of vRouter for vlan tag update
            # we will limit any vlan update once a port has been made
            # a sub-interface
            if (new_vlan != old_vlan and
                    'virtual_machine_interface_refs' in read_result):
                return (False,
                        (400, "Cannot change sub-interface VLAN tag ID"),
                        zk_update_kwargs)

        ret_dict = None
        if kvps:
            kvp_dict = cls._kvp_to_dict(kvps)
            new_vnic_type = kvp_dict.get('vnic_type', old_vnic_type)
            # IRONIC: allow for normal->baremetal change if bindings has
            # link-local info
            if new_vnic_type != 'baremetal':
                if (old_vnic_type != new_vnic_type):
                    if cls._is_port_bound(read_result, kvp_dict):
                        msg = ("Vnic_type can not be modified when port is "
                               "linked to Vrouter or VM.")
                        return False, (409, msg), zk_update_kwargs

            # Manage the bindings for baremetal servers
            vnic_type = kvp_dict.get('vnic_type')
            links = []
            vpg_name_db = None
            if read_result.get('virtual_port_group_back_refs'):
                vpg_name_db = (read_result.get(
                    'virtual_port_group_back_refs')[0]['to'][-1])
                obj_dict['virtual_port_group_name'] = vpg_name_db
            # For Ironic user, port update does not include 'vpg' info
            # in bindings. Retrieve vpg_name from db if not coming in the
            # payload. CEM-21992
            vpg_name = kvp_dict.get('vpg') or vpg_name_db

            if vnic_type == 'baremetal':
                ok, (vlan_id, is_untagged_vlan, links) = \
                    cls.get_vlan_phy_links(kvps, obj_dict)
                if not ok:
                    return (False, (400, 'Cannot get vlan or phy_links'),
                            zk_update_kwargs)

                if (vlan_id != old_vlan and
                   'virtual_machine_interface_refs' in read_result):
                    return (False, (400, "Cannot change VLAN ID"),
                            zk_update_kwargs)

                # Read VLAN_type from db
                db_is_untagged_vlan = None
                db_bindings = read_result.get(
                    'virtual_machine_interface_bindings', {})
                db_kvps = db_bindings.get('key_value_pair', [])
                db_kvps_dict = cls._kvp_to_dict(db_kvps)
                if db_kvps_dict.get('tor_port_vlan_id', 0):
                    db_is_untagged_vlan = True
                elif old_vlan:
                    db_is_untagged_vlan = False

                if kvp_dict.get('profile'):
                    phy_links = json.loads(kvp_dict.get('profile'))
                    if phy_links and phy_links.get('local_link_information'):
                        links = phy_links['local_link_information']
                        # if profile = "'{}'", then assume its from coming from
                        # ironic user, conveying to remove PIs from VPG
                        if (not links and
                           (kvp_dict.get('profile') != '{}')):
                            links = read_result.get('local_link_information')

                # manage_vpg function needs to be called regardless
                # of phy_links
                vpg_refs = obj_dict.get('virtual_port_group_refs') or None
                # For Ironic user, port update does not include vpg_refs.
                # vpg_refs are required in _manage_vpg_association for
                # retreiving fabric name for running fabric validations
                # CEM-21992
                if not vpg_refs:
                    vpg_refs = read_result.get('virtual_port_group_back_refs')

                # check if any of the phy_links has ref to VPG
                # if yes, retrive that vpg ref - CEM-22753
                if links and not vpg_refs:
                    for link in links:
                        # retrieving one PI is good enough
                        pi_name = link['port_id']
                        pr_name = link['switch_info']
                        pi_fq_name = ['default-global-system-config',
                                      pr_name, pi_name]
                        try:
                            pi_uuid = db_conn.fq_name_to_uuid(
                                'physical_interface',
                                pi_fq_name)
                        except NoIdError:
                            msg = ('Retrive physical_interface (%s) failed '
                                   'while trying to read vpg refs'
                                   % pi_fq_name)
                            return (False, (404, msg))
                        ok, pi_db_result = db_conn.dbe_read(
                            obj_type='physical_interface',
                            obj_id=pi_uuid,
                            obj_fields=['virtual_port_group_back_refs'])
                        if not ok:
                            return ok, pi_db_result
                        vpg_refs = pi_db_result.get(
                            'virtual_port_group_back_refs')
                        if vpg_refs:
                            vpg_name = vpg_refs[0]['to'][-1]
                        break

                vpg_uuid, ret_dict, zk_update_kwargs = \
                    cls._manage_vpg_association(
                        id, cls.server, db_conn, links,
                        vpg_refs, db_vlan_id=old_vlan,
                        vpg_name=vpg_name, vn_uuid=vn_uuid,
                        vlan_id=vlan_id,
                        is_untagged_vlan=is_untagged_vlan,
                        db_is_untagged_vlan=db_is_untagged_vlan)
                if not vpg_uuid:
                    return vpg_uuid, ret_dict, zk_update_kwargs
                obj_dict['port_virtual_port_group_id'] = vpg_uuid
                if not read_result.get('virtual_port_group_refs', None):
                    # Add reference from this VMI to VPG, if not added
                    # already
                    obj_dict = cls.add_vpg_ref(obj_dict,
                                               vpg_uuid,
                                               vlan_id,
                                               is_untagged_vlan,
                                               db_conn)

        if old_vnic_type == cls.portbindings['VNIC_TYPE_DIRECT']:
            cls._check_vrouter_link(read_result, kvp_dict, obj_dict, db_conn)

        if 'virtual_machine_interface_bindings' in obj_dict or vmib:
            bindings_port = read_result.get(
                'virtual_machine_interface_bindings', {})
            kvps_port = bindings_port.get('key_value_pair') or []
            kvp_dict_port = cls._kvp_to_dict(kvps_port)
            kvp_dict = cls._kvp_to_dict(kvps)
            if (kvp_dict_port.get('vnic_type') ==
                    cls.portbindings['VNIC_TYPE_VIRTIO_FORWARDER'] and
                    kvp_dict.get('host_id') != 'null'):
                vif_type = {
                    'key': 'vif_type',
                    'value': cls.portbindings['VIF_TYPE_VROUTER'],
                }
                vif_params = {
                    cls.portbindings['VHOST_USER_MODE']:
                        cls.portbindings['VHOST_USER_MODE_SERVER'],
                    cls.portbindings['VHOST_USER_SOCKET']:
                        cls._get_port_vhostuser_socket_name(id),
                    cls.portbindings['VHOST_USER_VROUTER_PLUG']: True,
                }
                vif_details = {
                    'key': 'vif_details',
                    'value': json.dumps(vif_params),
                }
                cls._kvps_prop_update(obj_dict, kvps, prop_collection_updates,
                                      vif_type, vif_details, True)
            elif ((kvp_dict_port.get('vnic_type') ==
                    cls.portbindings['VNIC_TYPE_NORMAL'] or
                    kvp_dict_port.get('vnic_type') is None) and
                    kvp_dict.get('host_id') != 'null'):
                (ok, result) = cls._is_dpdk_enabled(obj_dict, db_conn,
                                                    kvp_dict.get('host_id'))
                if not ok:
                    return ok, result, zk_update_kwargs
                elif result:
                    vif_type = {
                        'key': 'vif_type',
                        'value': cls.portbindings['VIF_TYPE_VHOST_USER'],
                    }
                    vif_params = {
                        cls.portbindings['VHOST_USER_MODE']:
                            cls.portbindings['VHOST_USER_MODE_SERVER'],
                        cls.portbindings['VHOST_USER_SOCKET']:
                            cls._get_port_vhostuser_socket_name(id),
                        cls.portbindings['VHOST_USER_VROUTER_PLUG']: True,
                    }
                    vif_details = {
                        'key': 'vif_details',
                        'value': json.dumps(vif_params),
                    }
                    cls._kvps_prop_update(
                        obj_dict,
                        kvps,
                        prop_collection_updates,
                        vif_type,
                        vif_details,
                        True)
                else:
                    vif_type = {'key': 'vif_type', 'value': None}
                    vif_details = {'key': 'vif_details', 'value': None}
                    cls._kvps_prop_update(
                        obj_dict,
                        kvps,
                        prop_collection_updates,
                        vif_type,
                        vif_details,
                        False)
            else:
                vif_type = {'key': 'vif_type', 'value': None}
                vif_details = {'key': 'vif_details', 'value': None}
                if (kvp_dict_port.get('vnic_type') !=
                        cls.portbindings['VNIC_TYPE_DIRECT']):
                    if obj_dict and 'vif_details' in kvp_dict_port:
                        cls._kvps_update(kvps, vif_type)
                        cls._kvps_update(kvps, vif_details)
                    elif kvp_dict.get('host_id') == 'null':
                        vif_details_prop = {
                            'field': 'virtual_machine_interface_bindings',
                            'operation': 'delete',
                            'value': vif_details,
                            'position': 'vif_details',
                        }
                        vif_type_prop = {
                            'field': 'virtual_machine_interface_bindings',
                            'operation': 'delete',
                            'value': vif_type,
                            'position': 'vif_type',
                        }
                        if prop_collection_updates is not None:
                            if vif_details_prop is not None:
                                prop_collection_updates.append(
                                    vif_details_prop)
                            if vif_type_prop is not None:
                                prop_collection_updates.append(vif_type_prop)

        ok, result = cls._check_port_security_and_address_pairs(obj_dict,
                                                                read_result)
        if not ok:
            return ok, result, zk_update_kwargs

        ok, result = cls._check_service_health_check_type(
            obj_dict, read_result, db_conn)
        if not ok:
            return ok, result, zk_update_kwargs

        return True, ret_dict, zk_update_kwargs

    @classmethod
    def _format_sp_untagged_znode(cls, vpg_uuid):
        return os.path.join(
            _DEFAULT_ZK_FABRIC_SP_PATH_PREFIX,
            'virtual-port-group:%s' % vpg_uuid,
            'untagged')

    @classmethod
    def _format_sp_tagged_znode(cls, vpg_uuid, vn_uuid, vlan_id):
        return os.path.join(
            _DEFAULT_ZK_FABRIC_SP_PATH_PREFIX,
            'virtual-port-group:%s' % vpg_uuid,
            'virtual-network:%s' % vn_uuid,
            'vlan:%s' % vlan_id)

    @classmethod
    def _check_serviceprovider_fabric(
            cls, db_conn, api_server, db_vlan_id, vpg_uuid, vn_uuid,
            vlan_id, vmi_uuid, is_untagged_vlan, db_is_untagged_vlan):
        """Verify Service Provider Style Fabric's restrictions.

        :Args
        :  db_conn: db connection object
        :  db_vlan_id: old vlan read from ZK
        :  api_server: api_server connection object
        :  vpg_uuid: UUID of VPG
        :  vn_uuid: UUID of virtual_network
        :  vlan_id: ID of the VLAN
        :  vmi_uuid: UUID of virtual_machine_interface
        :  is_untagged_vlan:
        :    True: untagged
        :    False: tagged
        :  db_is_untagged_vlan: old is_untagged_vlan from ZK
        :Returns
        :  (True, '', {delete_validation_znode': db_vlan_id}): PASS condition
        :  (False, (<code>, msg), None): Fail condition
        """
        # Verify we received a valid vlan-id. May be a VMI update
        if not vlan_id:
            return True, '', None

        if is_untagged_vlan:  # untagged VLAN
            vmi_lock_args = {'zookeeper_client': db_conn._zk_db._zk_client,
                             'path': os.path.join(
                                 api_server.fabric_validation_lock_prefix,
                                 'serviceprovider', vpg_uuid, 'untagged'),
                             'name': vmi_uuid, 'timeout': 60}
            validation_znode = cls._format_sp_untagged_znode(
                vpg_uuid)
        else:  # tagged VLAN
            vmi_lock_args = {'zookeeper_client': db_conn._zk_db._zk_client,
                             'path': os.path.join(
                                 api_server.fabric_validation_lock_prefix,
                                 'serviceprovider', vpg_uuid, vn_uuid,
                                 str(vlan_id)),
                             'name': vmi_uuid, 'timeout': 60}
            validation_znode = cls._format_sp_tagged_znode(
                vpg_uuid, vn_uuid, vlan_id)

        try:
            cls.db_conn._zk_db._zk_client.create_node(
                validation_znode, value=vmi_uuid)

            def undo_create_sp_validation_node():
                cls.db_conn._zk_db._zk_client.delete_node(
                    validation_znode, True)
                return True, "", None
            get_context().push_undo(undo_create_sp_validation_node)

            # Check for VLAN_id has updated for tagged VMI
            if db_vlan_id and (db_vlan_id != vlan_id):
                if not is_untagged_vlan:
                    db_validation_znode =\
                        cls._format_sp_tagged_znode(
                            vpg_uuid, vn_uuid, db_vlan_id)
                    cls.db_conn._zk_db._zk_client.update_node(
                        db_validation_znode,
                        repr(vmi_uuid).encode('ascii'))
                return True, '', None

            # Check if VLAN_type has changed
            if db_is_untagged_vlan is not None:
                # untagged to tagged
                if (not is_untagged_vlan and db_is_untagged_vlan):
                    untagged_validation_znode = \
                        cls._format_sp_untagged_znode(vpg_uuid)
                    return (True, '',
                            {'delete_validation_znode':
                             [untagged_validation_znode]})

        except ResourceExistsError:  # Might be STALE
            try:
                zk_vmi_uuid = cls.db_conn._zk_db._zk_client.read_node(
                    validation_znode)
                _ = db_conn.uuid_to_fq_name(zk_vmi_uuid)  # NOT STALE

                # Check if VLAN_type has changed
                if db_is_untagged_vlan is not None:
                    # tagged to untagged
                    if (is_untagged_vlan and not db_is_untagged_vlan):
                        tagged_validation_znode = \
                            cls._format_sp_tagged_znode(
                                vpg_uuid, vn_uuid, db_vlan_id)
                        return (True, '',
                                {'delete_validation_znode':
                                 [tagged_validation_znode]})

                # Check for VLAN_id has updated for untagged VMI
                # No changes required here
                if is_untagged_vlan and db_is_untagged_vlan:
                    return True, '', None

                if is_untagged_vlan:
                    msg = ("Virtual machine interface:(%s) already exists "
                           "for virtual port group:(%s)" %
                           (zk_vmi_uuid, vpg_uuid))
                else:
                    msg = ("Virtual machine interface:(%s) already exists "
                           "in virtual network:(%s) with vlan(%s)" %
                           (zk_vmi_uuid, vn_uuid, vlan_id))
                return False, (400, msg), None

            except NoIdError:  # STALE
                with ZookeeperLock(**vmi_lock_args):
                    cls.db_conn._zk_db._zk_client.update_node(
                        validation_znode, repr(vmi_uuid).encode('ascii'))

        return True, '', None

    @classmethod
    def _format_ep_untagged_znode(cls, vpg_uuid):
        return os.path.join(
            _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
            'virtual-port-group:%s' % vpg_uuid,
            'untagged')

    @classmethod
    def _format_ep_tagged_vpg_vn_znode(cls, vpg_uuid, vn_uuid):
        return os.path.join(
            _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
            'virtual-port-group:%s' % vpg_uuid,
            'virtual-network:%s' % vn_uuid)

    @classmethod
    def _format_ep_tagged_vpg_vlan_znode(cls, vpg_uuid, vlan_id):
        return os.path.join(
            _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
            'virtual-port-group:%s' % vpg_uuid,
            'vlan:%s' % vlan_id)

    @classmethod
    def _format_ep_tagged_fabric_vn_znode(cls, fabric_uuid, vn_uuid):
        return os.path.join(
            _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
            'fabric:%s' % fabric_uuid,
            'virtual-network:%s' % vn_uuid)

    @classmethod
    def _format_ep_tagged_fabric_vlan_znode(cls, fabric_uuid, vlan_id):
        return os.path.join(
            _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
            'fabric:%s' % fabric_uuid,
            'vlan:%s' % vlan_id)

    @classmethod
    def _create_enterprise_fabric_validation_vmi_fabric_znodes(
            cls, db_conn, fabric_uuid, vn_uuid, vlan_id):
        # Create fabric Znodes
        fabric_vn_validation_znode = \
            cls._format_ep_tagged_fabric_vn_znode(
                fabric_uuid, vn_uuid)
        fabric_vlan_validation_znode = \
            cls._format_ep_tagged_fabric_vlan_znode(
                fabric_uuid, vlan_id)

        try:  # fabric_vn_validation_node
            cls.db_conn._zk_db._zk_client.create_node(
                fabric_vn_validation_znode, value=vlan_id)

            def undo_create_enterprise_fabric_vn_validation_node():
                cls.db_conn._zk_db._zk_client.delete_node(
                    fabric_vn_validation_znode, True)
                return True, ''

            get_context().push_undo(
                undo_create_enterprise_fabric_vn_validation_node)

            try:  # fabric_vlan_validation_node
                cls.db_conn._zk_db._zk_client.create_node(
                    fabric_vlan_validation_znode, value=vn_uuid)

                def undo_create_enterprise_fabric_vlan_validation_node():
                    cls.db_conn._zk_db._zk_client.delete_node(
                        fabric_vlan_validation_znode, True)
                    return True, ''

                get_context().push_undo(
                    undo_create_enterprise_fabric_vlan_validation_node)

            except ResourceExistsError:  # fabric_vlan_validation_znode
                zk_fabric_vn_uuid = \
                    cls.db_conn._zk_db._zk_client.read_node(
                        fabric_vlan_validation_znode)
                if zk_fabric_vn_uuid == vn_uuid:  # Same VN-VLAN exist
                    return True, ''
                else:
                    cls.db_conn._zk_db._zk_client.delete_node(
                        fabric_vn_validation_znode, True)
                    msg = ("Virtual network:(%s) with other vlan(%s) "
                           "already exists in fabric:(%s)" %
                           (vn_uuid, vlan_id, fabric_uuid))
                    return False, (400, msg)

        except ResourceExistsError:  # fabric_vn_validation_znode
            zk_fabric_vlan_id = cls.db_conn._zk_db._zk_client.read_node(
                fabric_vn_validation_znode)
            if zk_fabric_vlan_id == str(vlan_id):  # Same VN-VLAN exists
                return True, ''
            else:
                msg = ("Virtual network:(%s) with other vlan(%s) "
                       "already exists in fabric:(%s)" %
                       (vn_uuid, vlan_id, fabric_uuid))
                return False, (400, msg)

        return True, ''

    @classmethod
    def _create_fabric_enterprise_vmi_znodes(
            cls, db_conn, api_server, db_vlan_id, fabric_uuid, vpg_uuid,
            vn_uuid, vlan_id, vmi_uuid, req_is_untagged_vlan,
            db_is_untagged_vlan):

        vlan_lock_args = {'zookeeper_client': db_conn._zk_db._zk_client,
                          'path': os.path.join(
                              api_server.fabric_validation_lock_prefix,
                              'enterprise', vpg_uuid, str(vlan_id)),
                          'name': vmi_uuid, 'timeout': 60}
        vlan_validation_znode = cls._format_ep_tagged_vpg_vlan_znode(
            vpg_uuid, vlan_id)

        try:  # vlan_validation_znode
            cls.db_conn._zk_db._zk_client.create_node(
                vlan_validation_znode, value=vmi_uuid)

            def undo_create_enterprise_vlan_validation_node():
                cls.db_conn._zk_db._zk_client.delete_node(
                    vlan_validation_znode, True)
                return True, '', None

            get_context().push_undo(
                undo_create_enterprise_vlan_validation_node)

            # Check for VLAN_id has updated for tagged VMI
            if db_vlan_id and (db_vlan_id != vlan_id):
                if not req_is_untagged_vlan:
                    db_vlan_validation_znode = \
                        cls._format_ep_tagged_vpg_vlan_znode(
                            vpg_uuid, db_vlan_id)
                    db_vn_validation_znode = \
                        cls._format_ep_tagged_vpg_vn_znode(
                            vpg_uuid, vn_uuid)
                    db_fabric_vn_validation_znode = \
                        cls._format_ep_tagged_fabric_vn_znode(
                            fabric_uuid, vn_uuid)
                    db_fabric_vlan_validation_znode = \
                        cls._format_ep_tagged_fabric_vlan_znode(
                            fabric_uuid, db_vlan_id)
                    # Update vn_validation_znode with new vlan_id
                    cls.db_conn._zk_db._zk_client.update_node(
                        db_vn_validation_znode,
                        str(vlan_id))
                    # Update fabric_vn_validation_znode with new vlan_id
                    cls.db_conn._zk_db._zk_client.update_node(
                        db_fabric_vn_validation_znode,
                        str(vlan_id))
                    # Create new fabric_vlan_validation_znode with new vlan_id
                    fabric_vlan_validation_znode = \
                        cls._format_ep_tagged_fabric_vlan_znode(
                            fabric_uuid, vlan_id)

                    try:  # fabric_vlan_validation_node
                        cls.db_conn._zk_db._zk_client.create_node(
                            fabric_vlan_validation_znode, value=vn_uuid)

                        def undo_create_enterprise_fabric_vlan_vld_node():
                            cls.db_conn._zk_db._zk_client.delete_node(
                                fabric_vlan_validation_znode, True)
                            return True, '', None

                        get_context().push_undo(
                            undo_create_enterprise_fabric_vlan_vld_node)

                    except ResourceExistsError:  # fabric_vlan_validation_znode
                        zk_fabric_vn_uuid = \
                            cls.db_conn._zk_db._zk_client.read_node(
                                fabric_vlan_validation_znode)
                        fabric_vn_validation_znode = \
                            cls._format_ep_tagged_fabric_vlan_znode(
                                fabric_uuid, vn_uuid)
                        if zk_fabric_vn_uuid == vn_uuid:  # Same VN-VLAN exist
                            return (True, '',
                                    {'delete_validation_znode':
                                     [db_vlan_validation_znode,
                                      db_fabric_vlan_validation_znode]})
                        else:
                            cls.db_conn._zk_db._zk_client.delete_node(
                                fabric_vn_validation_znode, True)
                            msg = ("Virtual network:(%s) with other vlan(%s) "
                                   "already exists in fabric:(%s)" %
                                   (vn_uuid, vlan_id, fabric_uuid))
                            return (False, (400, msg), None)

                    return (True, '',
                            {'delete_validation_znode':
                             [db_vlan_validation_znode,
                              db_fabric_vlan_validation_znode]})

            ok, result = \
                cls._create_enterprise_fabric_validation_vmi_fabric_znodes(
                    db_conn, fabric_uuid, vn_uuid, vlan_id)
            if not ok:
                return False, result, None

            # Check if VLAN_type has changed
            if db_is_untagged_vlan is not None:
                # untagged to tagged
                if (not req_is_untagged_vlan and db_is_untagged_vlan):
                    # Return deletion of znode (untagged)
                    db_untagged_validation_znode = \
                        cls._format_ep_untagged_znode(vpg_uuid)
                    return (True, '',
                            {'delete_validation_znode':
                             [db_untagged_validation_znode]})

        except ResourceExistsError:  # Might be STALE (vlan_validation_znode)
            try:
                zk_vmi_uuid = cls.db_conn._zk_db._zk_client.read_node(
                    vlan_validation_znode)
                _ = db_conn.uuid_to_fq_name(zk_vmi_uuid)  # NOT STALE

                msg = ("Virtual machine interface:(%s) already exists "
                       "in virtual network:(%s) with vlan(%s)" %
                       (zk_vmi_uuid, vn_uuid, vlan_id))
                return (False, (400, msg), None)

            except NoIdError:  # STALE
                with ZookeeperLock(**vlan_lock_args):
                    cls.db_conn._zk_db._zk_client.update_node(
                        vlan_validation_znode, repr(vmi_uuid).encode('ascii'))

        return True, '', None

    @classmethod
    def _create_enterprise_fabric_validation_tagged_znodes(
            cls, db_conn, api_server, fabric_uuid, vpg_uuid, vn_uuid, vlan_id,
            db_vlan_id, vmi_uuid, req_is_untagged_vlan, db_is_untagged_vlan):

        vn_lock_args = {'zookeeper_client': db_conn._zk_db._zk_client,
                        'path': os.path.join(
                            api_server.fabric_validation_lock_prefix,
                            'enterprise', fabric_uuid, vn_uuid),
                        'name': vlan_id, 'timeout': 60}
        vn_validation_znode = \
            cls._format_ep_tagged_vpg_vn_znode(vpg_uuid, vn_uuid)

        with ZookeeperLock(**vn_lock_args):
            try:
                cls.db_conn._zk_db._zk_client.create_node(
                    vn_validation_znode, value=vlan_id)

                def undo_create_enterprise_vn_validation_node():
                    cls.db_conn._zk_db._zk_client.delete_node(
                        vn_validation_znode, True)
                    return True, '', None
                get_context().push_undo(
                    undo_create_enterprise_vn_validation_node)

                ok, result, del_znode_kwargs = \
                    cls._create_fabric_enterprise_vmi_znodes(
                        db_conn, api_server, db_vlan_id, fabric_uuid,
                        vpg_uuid, vn_uuid, vlan_id, vmi_uuid,
                        req_is_untagged_vlan, db_is_untagged_vlan)
                if not ok:
                    return False, result, del_znode_kwargs
                else:
                    return True, '', del_znode_kwargs

            except ResourceExistsError:  # Might be STALE
                try:
                    zk_vlan_id = cls.db_conn._zk_db._zk_client.read_node(
                        vn_validation_znode)
                    db_vlan_validation_znode = \
                        cls._format_ep_tagged_vpg_vlan_znode(
                            vpg_uuid, zk_vlan_id)
                    zk_vmi_uuid = cls.db_conn._zk_db._zk_client.read_node(
                        db_vlan_validation_znode)

                    # NOT STALE
                    # Check if it is a BAD request
                    if zk_vmi_uuid != vmi_uuid:
                        msg = ("vlan:(%s) already exists in virtual port "
                               "group:(%s) with virtual network:(%s)" %
                               (zk_vlan_id, vpg_uuid, vn_uuid))
                        return False, (400, msg), None

                    # Check for VLAN_id update
                    if zk_vlan_id != str(vlan_id):
                        ok, result, del_znode_kwargs = \
                            cls._create_fabric_enterprise_vmi_znodes(
                                db_conn, api_server, db_vlan_id, fabric_uuid,
                                vpg_uuid, vn_uuid, vlan_id, vmi_uuid,
                                req_is_untagged_vlan, db_is_untagged_vlan)
                        if not ok:
                            return False, result, del_znode_kwargs
                        else:
                            return True, '', del_znode_kwargs

                except NoIdError:  # STALE
                    cls.db_conn._zk_db._zk_client.update_node(
                        vn_validation_znode, str(vlan_id))

                    ok, result, del_znode_kwargs = \
                        cls._create_fabric_enterprise_vmi_znodes(
                            db_conn, api_server, db_vlan_id, fabric_uuid,
                            vpg_uuid, vn_uuid, vlan_id, vmi_uuid,
                            req_is_untagged_vlan, db_is_untagged_vlan)
                    if not ok:
                        return False, result, del_znode_kwargs
                    else:
                        return True, '', del_znode_kwargs

        return True, '', None

    @classmethod
    def _create_enterprise_fabric_validation_untagged_znodes(
            cls, db_conn, api_server, fabric_uuid, vpg_uuid, vn_uuid, vlan_id,
            vmi_uuid, req_is_untagged_vlan, db_is_untagged_vlan):

        vmi_lock_args = {'zookeeper_client': db_conn._zk_db._zk_client,
                         'path': os.path.join(
                             api_server.fabric_validation_lock_prefix,
                             'enterprise', vpg_uuid, 'untagged'),
                         'name': vmi_uuid, 'timeout': 60}
        untagged_vmi_validation_znode = \
            cls._format_ep_untagged_znode(vpg_uuid)

        try:
            cls.db_conn._zk_db._zk_client.create_node(
                untagged_vmi_validation_znode, value=vmi_uuid)

            def undo_create_enterprise_validation_node():
                cls.db_conn._zk_db._zk_client.delete_node(
                    untagged_vmi_validation_znode, True)
                return True, "", None

            get_context().push_undo(
                undo_create_enterprise_validation_node)

            # Check if VLAN_type has changed
            if db_is_untagged_vlan is not None:
                # tagged to untagged
                if (req_is_untagged_vlan and not db_is_untagged_vlan):
                    # return list of tagged nodes to be deleted
                    # during post_dbe_update
                    db_vn_validation_znode = \
                        cls._format_ep_tagged_vpg_vn_znode(
                            vpg_uuid, vn_uuid)
                    db_vlan_validation_znode = \
                        cls._format_ep_tagged_vpg_vlan_znode(
                            vpg_uuid, vlan_id)
                    db_fabric_vn_validation_znode = \
                        cls._format_ep_tagged_fabric_vn_znode(
                            fabric_uuid, vn_uuid)
                    db_fabric_vlan_validation_znode = \
                        cls._format_ep_tagged_fabric_vlan_znode(
                            fabric_uuid, vlan_id)

                    return (True, '',
                            {'delete_validation_znode':
                             [db_vn_validation_znode,
                              db_vlan_validation_znode,
                              db_fabric_vn_validation_znode,
                              db_fabric_vlan_validation_znode]})

        except ResourceExistsError:  # Might be STALE (Untagged)
            try:
                zk_vmi_uuid = cls.db_conn._zk_db._zk_client.read_node(
                    untagged_vmi_validation_znode)
                _ = db_conn.uuid_to_fq_name(zk_vmi_uuid)  # NOT STALE

                # Check if VLAN_type has changed
                # tagged to untagged
                if db_is_untagged_vlan and \
                        (req_is_untagged_vlan and not db_is_untagged_vlan):
                    try:
                        cls.db_conn._zk_db._zk_client.create_node(
                            untagged_vmi_validation_znode, value=vmi_uuid)

                        def undo_create_enterprise_validation_node():
                            cls.db_conn._zk_db._zk_client.delete_node(
                                untagged_vmi_validation_znode, True)
                            return True, "", None

                        get_context().push_undo(
                            undo_create_enterprise_validation_node)

                        # return list of tagged nodes to be deleted
                        # during post_dbe_update
                        db_vn_validation_znode = \
                            cls._format_ep_tagged_vpg_vn_znode(
                                vpg_uuid, vn_uuid)
                        db_vlan_validation_znode = \
                            cls._format_ep_tagged_vpg_vlan_znode(
                                vpg_uuid, vlan_id)
                        db_fabric_vn_validation_znode = \
                            cls._format_ep_tagged_fabric_vn_znode(
                                fabric_uuid, vn_uuid)
                        db_fabric_vlan_validation_znode = \
                            cls._format_ep_tagged_fabric_vlan_znode(
                                fabric_uuid, vlan_id)

                        return (True, '',
                                {'delete_validation_znode':
                                 [db_vn_validation_znode,
                                  db_vlan_validation_znode,
                                  db_fabric_vn_validation_znode,
                                  db_fabric_vlan_validation_znode]})

                    except ResourceExistsError:
                        return False, '', None

                # Check for VLAN_id has updated for untagged VMI
                # No changes required here
                if req_is_untagged_vlan and db_is_untagged_vlan:
                    return True, '', None

                msg = ("Virtual machine interface:(%s) already exists "
                       "with virtual network:(%s) with vlan(%s)" %
                       (zk_vmi_uuid, vn_uuid, vlan_id))
                return False, (400, msg), None

            except NoIdError:  # STALE
                with ZookeeperLock(**vmi_lock_args):
                    cls.db_conn._zk_db._zk_client.update_node(
                        untagged_vmi_validation_znode,
                        repr(vmi_uuid).encode('ascii'))

        return True, '', None

    @classmethod
    def _check_enterprise_fabric(
            cls, db_conn, api_server, db_vlan_id, vpg_uuid, fabric_uuid,
            vn_uuid, vlan_id, vmi_uuid, req_is_untagged_vlan,
            db_is_untagged_vlan):
        """Verify Enterprise Style Fabric's restrictions.

        :Args
        :  db_conn: db connection object
        :  api_server: api_server connection object
        :  db_vlan_id: old vlan from ZK
        :  vpg_uuid: UUID of VPG
        :  vn_uuid: UUID of virtual_network
        :  vlan_id: ID of the VLAN
        :  vmi_uuid: UUID of virtual_machine_interface
        :  req_is_untagged_vlan:
        :    True: untagged
        :    False: tagged
        :  db_is_untagged_vlan: old is_untagged_vlan from ZK
        :Returns
        :  (True, '', {delete_validation_znode': db_vlan_id}): PASS condition
        :  (False, (<code>, msg), None): Fail condition
        """
        # check vlan-id received is valid or VMI Update
        if not vlan_id:
            return True, '', None

        del_validation_znodes = None
        if req_is_untagged_vlan:  # untagged VLAN
            ok, result, del_validation_znodes = \
                cls._create_enterprise_fabric_validation_untagged_znodes(
                    db_conn, api_server, fabric_uuid, vpg_uuid, vn_uuid,
                    vlan_id, vmi_uuid, req_is_untagged_vlan,
                    db_is_untagged_vlan)
            if not ok:
                return False, result, del_validation_znodes

        else:  # tagged VLAN
            ok, result, del_validation_znodes = \
                cls._create_enterprise_fabric_validation_tagged_znodes(
                    db_conn, api_server, fabric_uuid, vpg_uuid, vn_uuid,
                    vlan_id, db_vlan_id, vmi_uuid, req_is_untagged_vlan,
                    db_is_untagged_vlan)
            if not ok:
                return False, result, del_validation_znodes

        return True, '', del_validation_znodes

    @classmethod
    def _manage_vpg_association(cls, vmi_id, api_server, db_conn,
                                phy_links=None, vpg_refs_dict=None,
                                db_vlan_id=None, vpg_name=None, vn_uuid=None,
                                vlan_id=None, is_untagged_vlan=False,
                                db_is_untagged_vlan=None):
        fabric_name = None
        phy_interface_uuids = []
        old_phy_interface_uuids = []
        new_pi_to_pr_dict = {}
        zk_update_kwargs = {}
        for link in phy_links:
            if link.get('fabric'):
                if fabric_name is not None and fabric_name != link['fabric']:
                    msg = 'Physical interfaces in the same vpg '\
                          'should belong to the same fabric'
                    return (False, (400, msg), zk_update_kwargs)
                fabric_name = link['fabric']
            else:  # use default fabric if it's not in link local information
                fabric_name = 'default-fabric'
                # Use from vpg_refs_dict if any - CEM-22753
                if vpg_refs_dict:
                    fabric_name = vpg_refs_dict[0]['to'][-2]

            phy_interface_name = link['port_id']
            prouter_name = link['switch_info']
            pi_fq_name = ['default-global-system-config', prouter_name,
                          phy_interface_name]

            try:
                pi_uuid = db_conn.fq_name_to_uuid('physical_interface',
                                                  pi_fq_name)
                phy_interface_uuids.append(pi_uuid)
                new_pi_to_pr_dict[pi_uuid] = prouter_name
            except NoIdError:
                temp_vpg_name = vpg_name or "vpg-internal-missing"
                msg = 'Unable to attach Physical interface '
                msg += ('to Virtual Port Group (%s). '
                        'No such Physical Interface object (%s) exists' % (
                            temp_vpg_name, pi_fq_name[-1]))
                return (False, (404, msg), zk_update_kwargs)

        # check if fabric_name is None. If None, try retreiving fabric_name
        # from vpg_refs_dict
        if not fabric_name:
            if not vpg_refs_dict:
                msg = ('Unable to retrieve fabric name. No physical interfaces'
                       ' and no vpg refs')
                return (False, (400, msg), zk_update_kwargs)
            vpg_ref_uuid = vpg_refs_dict[0].get('uuid')
            if not vpg_ref_uuid:
                msg = 'Failed to retrieve fabric name. No vpg refs found'
                return (False, (400, msg), zk_update_kwargs)
            # Now, retrieve fabric_name from vpg_ref_uuid
            ok, virtual_port_group_dict = db_conn.dbe_read(
                obj_type='virtual_port_group',
                obj_id=vpg_ref_uuid,
                obj_fields=['parent_type'])
            if not ok:
                return (ok, (400, virtual_port_group_dict), zk_update_kwargs)

            parent_val = virtual_port_group_dict.get('parent_type')
            if parent_val == 'fabric':
                fabric_uuid = virtual_port_group_dict['parent_uuid']
                fabric_fq_name = db_conn.uuid_to_fq_name(fabric_uuid)
                fabric_name = fabric_fq_name[-1]
            else:
                msg = ('VPG do not belong to fabric object, '
                       'Unable to retrieve fabric name')
                return (False, (400, msg), zk_update_kwargs)

        # check if new physical interfaces belongs to some other vpg
        for uuid in set(phy_interface_uuids):
            ok, phy_interface_dict = db_conn.dbe_read(
                obj_type='physical-interface',
                obj_id=uuid,
                obj_fields=['name', 'virtual_port_group_back_refs'])
            if not ok:
                return (ok, (400, phy_interface_dict), zk_update_kwargs)

            vpg_refs = phy_interface_dict.get('virtual_port_group_back_refs')
            if vpg_refs and vpg_refs[0]['to'][-1] != vpg_name:
                msg = 'Physical interface %s already belong to the vpg %s' %\
                      (phy_interface_dict.get(
                          'name', phy_interface_dict['fq_name']),
                       vpg_refs[0]['to'][-1])
                return (False, (400, msg), zk_update_kwargs)

        if vpg_name:  # read the vpg object
            vpg_fq_name = ['default-global-system-config', fabric_name,
                           vpg_name]
            try:
                vpg_uuid = db_conn.fq_name_to_uuid('virtual_port_group',
                                                   vpg_fq_name)
            except NoIdError:
                msg = 'Vpg object %s is not found' % vpg_name
                return (False, (404, msg), zk_update_kwargs)

            ok, vpg_dict = db_conn.dbe_read(
                obj_type='virtual-port-group', obj_id=vpg_uuid)

            if not ok:
                return (ok, (400, vpg_dict), zk_update_kwargs)
            # Two Use cases:
            # 1. Ironic Use case: When there is change in PI info, old PI needs
            #    to be removed from db and new ones should be updated. In case
            #    of empty profile, all PIs from db needs to be removed
            # 2. Non-Ironic Use case: It sends whole set of PIs that needs to
            #    be associated with VPG. The PIs which are not in this set
            #    should be removed from db.
            # Change in Ironic workflow caused this change: CEM-21992
            if (not phy_interface_uuids and
               (vpg_dict.get('virtual_port_group_user_created') is not False)):
                phy_interface_uuids = \
                    [pi['uuid']
                     for pi in vpg_dict.get('physical_interface_refs')]

        else:  # create vpg object
            fabric_fq_name = [
                'default-global-system-config',
                fabric_name,
                phy_interface_uuids[0],
            ]
            vpg_id = cls.vnc_zk_client.alloc_vpg_id(':'.join(fabric_fq_name))

            def undo_vpg_id():
                cls.vnc_zk_client.free_vpg_id(vpg_id, ':'.join(fabric_fq_name))
                return True, "", None
            get_context().push_undo(undo_vpg_id)

            vpg_name = "vpg-internal-" + str(vpg_id)
            vpg_obj = VirtualPortGroup(
                parent_type='fabric',
                fq_name=['default-global-system-config', fabric_name,
                         vpg_name],
                virtual_port_group_user_created=False,
                virtual_port_group_lacp_enabled=True)
            vpg_int_dict = json.dumps(vpg_obj, default=_obj_serializer_all)

            ok, resp = api_server.internal_request_create(
                'virtual-port-group', json.loads(vpg_int_dict))

            if not ok:
                return (ok, (400, resp), zk_update_kwargs)

            vpg_dict = resp['virtual-port-group']
            vpg_uuid = resp['virtual-port-group']['uuid']

            def undo_vpg_create():
                cls.server.internal_request_delete('virtual-port-group',
                                                   vpg_uuid)
                return True, '', zk_update_kwargs
            get_context().push_undo(undo_vpg_create)

        # Check for fabric to get vpg
        if 'parent_uuid' in vpg_dict:
            ok, read_result = cls.dbe_read(
                db_conn,
                'fabric',
                vpg_dict['parent_uuid'],
                obj_fields=['fabric_enterprise_style',
                            'virtual_port_groups',
                            'disable_vlan_vn_uniqueness_check'])
            if not ok:
                return ok, read_result, zk_update_kwargs
            fabric = read_result
            fabric_uuid = fabric.get('uuid')
            # In cases of upgrade, the fabric_enterprise_style flag won't
            # be set. In such cases, consider the flag to be false
            fabric_enterprise_style = (fabric.get('fabric_enterprise_style') or
                                       False)
            disable_vlan_vn_uniqueness_check = (fabric.get(
                'disable_vlan_vn_uniqueness_check') or False)

            # VLAN-ID sanitizer
            def vlanid_sanitizer(vlanid):
                if not vlanid:
                    return None
                elif (isinstance(vlanid, type('')) and
                      vlanid.isdigit()):
                    return int(vlanid)
                elif isinstance(vlanid, int):
                    return vlanid
            vlan_id = vlanid_sanitizer(vlan_id)

            if not disable_vlan_vn_uniqueness_check:
                if fabric_enterprise_style:
                    ok, result, zk_update_kwargs = \
                        cls._check_enterprise_fabric(
                            db_conn, api_server, db_vlan_id, vpg_uuid,
                            fabric_uuid, vn_uuid, vlan_id, vmi_id,
                            is_untagged_vlan, db_is_untagged_vlan)
                    if not ok:
                        return ok, result, zk_update_kwargs
                else:
                    ok, result, zk_update_kwargs = \
                        cls._check_serviceprovider_fabric(
                            db_conn, api_server, db_vlan_id, vpg_uuid,
                            vn_uuid, vlan_id, vmi_id, is_untagged_vlan,
                            db_is_untagged_vlan)
                    if not ok:
                        return ok, result, zk_update_kwargs

        old_phy_interface_uuids = []
        old_phy_interface_refs = vpg_dict.get('physical_interface_refs')
        old_phy_interface_uuids = [ref['uuid'] for ref in
                                   old_phy_interface_refs or []]
        ret_dict = {}

        # delete old physical interfaces to the vpg
        delete_pi_uuids = []
        delete_pi_uuids = (set(old_phy_interface_uuids) -
                           set(phy_interface_uuids))

        for uuid in delete_pi_uuids:
            try:
                api_server.internal_request_ref_update(
                    'virtual-port-group',
                    vpg_uuid,
                    'DELETE',
                    'physical-interface',
                    uuid)
            except Exception as exc:
                return False, (exc.status_code, exc.content)

        # add new physical interfaces to the vpg
        create_pi_uuids = []
        if phy_interface_uuids:
            create_pi_uuids = (set(phy_interface_uuids) -
                               set(old_phy_interface_uuids))
        for uuid in create_pi_uuids:
            try:
                api_server.internal_request_ref_update(
                    'virtual-port-group',
                    vpg_uuid,
                    'ADD',
                    'physical-interface',
                    uuid,
                    relax_ref_for_delete=True)
            except Exception as exc:
                return False, (exc.status_code, exc.content)

        # update intent-map with vn_id
        # read intent map object
        intent_map_name = fabric_name + '-assisted-replicator-intent-map'
        intent_map_fq_name = ['default-global-system-config',
                              intent_map_name]
        try:
            intent_map_uuid = db_conn.fq_name_to_uuid('intent_map',
                                                      intent_map_fq_name)
        except NoIdError:
            msg = 'Intent Map for Assisted Replicator object %s not ' \
                  'found for fabric %s' % (intent_map_fq_name[-1],
                                           fabric_name)
            return vpg_uuid, ret_dict, zk_update_kwargs

        api_server.internal_request_ref_update(
            'virtual-network',
            vn_uuid,
            'ADD',
            'intent-map',
            intent_map_uuid,
            relax_ref_for_delete=True)

        return vpg_uuid, ret_dict, zk_update_kwargs

    @classmethod
    def post_dbe_update(cls, id, fq_name, obj_dict, db_conn,
                        prop_collection_updates=None, ref_update=None,
                        **kwargs):

        api_server = db_conn.get_api_server()

        bindings = obj_dict.get('virtual_machine_interface_bindings', {})
        kvps = bindings.get('key_value_pair', [])
        # VMI-VLAN update case: delete the old ZNodes
        del_zk_node_path = kwargs.get('delete_validation_znode', None)
        if del_zk_node_path:
            for znode_path in del_zk_node_path:
                cls.db_conn._zk_db._zk_client.delete_node(znode_path, True)

        # ADD a ref from this VMI only if it's getting created
        # first time
        vpg_uuid = obj_dict.get('port_virtual_port_group_id')
        vpg_name = obj_dict.get('virtual_port_group_name')
        if not vpg_name and vpg_uuid:
            api_server.internal_request_ref_update(
                'virtual-port-group',
                vpg_uuid,
                'ADD',
                'virtual-machine-interface',
                id,
                relax_ref_for_delete=True)

        for oper_param in prop_collection_updates or []:
            if (oper_param['field'] == 'virtual_machine_interface_bindings' and
                    oper_param['operation'] == 'set'):
                kvps.append(oper_param['value'])

        return True, ''

    @classmethod
    def pre_dbe_delete(cls, id, obj_dict, db_conn):
        if ('virtual_machine_interface_refs' in obj_dict and
                'virtual_machine_interface_properties' in obj_dict):
            vmi_props = obj_dict['virtual_machine_interface_properties']
            if 'sub_interface_vlan_tag' not in vmi_props:
                msg = "Cannot delete vmi with existing ref to sub interface"
                return (False, (409, msg), None)

        bindings = obj_dict.get('virtual_machine_interface_bindings')
        if bindings:
            kvps = bindings.get('key_value_pair') or []
            kvp_dict = cls._kvp_to_dict(kvps)
            delete_dict = {'virtual_machine_refs': []}
            cls._check_vrouter_link(obj_dict, kvp_dict, delete_dict, db_conn)

        return True, "", None

    @classmethod
    def get_vn_id(cls, obj_dict, db_conn, vpg_uuid):
        vn_dict = obj_dict['virtual_network_refs'][0]
        vn_uuid = vn_dict.get('uuid')
        if not vn_uuid:
            vn_fq_name = vn_dict.get('to')
            if not vn_fq_name:
                msg = ("Virtual Machine Interface must have valid Virtual "
                       "Network reference")
                return False, (400, msg)
            vn_uuid = db_conn.fq_name_to_uuid('virtual_network', vn_fq_name)

        return True, vn_uuid

    @classmethod
    def get_vlan_phy_links(cls, kvps=None, obj_dict=None):
        vlan_id = None
        links = []
        is_untagged_vlan = False
        new_vlan = None
        if obj_dict:
            # Read tagged vlan_id
            new_vlan = (obj_dict.get(
                        'virtual_machine_interface_properties') or {}
                        ).get('sub_interface_vlan_tag') or 0
            bindings = obj_dict.get(
                'virtual_machine_interface_bindings') or {}
            if not kvps:
                kvps = bindings.get('key_value_pair') or []
        if kvps:
            kvp_dict = cls._kvp_to_dict(kvps)
            vnic_type = kvp_dict.get('vnic_type')
            tor_port_vlan_id = kvp_dict.get('tor_port_vlan_id', 0)
            if tor_port_vlan_id:
                vlan_id = tor_port_vlan_id
                is_untagged_vlan = True
            else:
                vlan_id = new_vlan
            if vnic_type == 'baremetal':
                if kvp_dict.get('profile'):
                    # Process only if port profile exists and physical links
                    # are specified
                    phy_links = json.loads(kvp_dict.get('profile'))
                    if phy_links and phy_links.get('local_link_information'):
                        links = phy_links['local_link_information']

        return True, (vlan_id, is_untagged_vlan, links)

    @classmethod
    def _delete_validations_znodes(cls, vpg_dict, obj_dict, db_conn,
                                   vpg_uuid):
        # Check fabric validation type
        if 'parent_uuid' in vpg_dict:
            ok, read_result = cls.dbe_read(
                db_conn,
                'fabric',
                vpg_dict['parent_uuid'],
                obj_fields=['fabric_enterprise_style'])
        if not ok:
            return ok, read_result, None
        fabric = read_result
        fabric_enterprise_style = (fabric.get('fabric_enterprise_style') or
                                   False)
        fabric_uuid = fabric.get('uuid')

        # Read virtual network uuid
        ok, result = cls.get_vn_id(obj_dict, db_conn, vpg_uuid)
        if not ok:
            return ok, result
        vn_uuid = result

        ok, (vlan_id, is_untagged_vlan, links) = cls.get_vlan_phy_links(
            None, obj_dict)
        if not ok:
            return ok, (400, "Cannot get vlan or phy_links")

        if not fabric_enterprise_style:
            # Path for ZNode deletion
            untagged_validation_path = os.path.join(
                _DEFAULT_ZK_FABRIC_SP_PATH_PREFIX,
                'virtual-port-group:%s' % vpg_uuid,
                'untagged')
            tagged_validation_path = os.path.join(
                _DEFAULT_ZK_FABRIC_SP_PATH_PREFIX,
                'virtual-port-group:%s' % vpg_uuid,
                'virtual-network:%s' % vn_uuid,
                'vlan:%s' % vlan_id)
        else:
            untagged_validation_path = os.path.join(
                _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
                'virtual-port-group:%s' % vpg_uuid,
                'untagged')
            tagged_vn_validation_path = os.path.join(
                _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
                'virtual-port-group:%s' % vpg_uuid,
                'virtual-network:%s' % vn_uuid)
            znode_vlan_id = db_conn._zk_db._zk_client.read_node(
                tagged_vn_validation_path)
            tagged_vlan_validation_path = os.path.join(
                _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
                'virtual-port-group:%s' % vpg_uuid,
                'vlan:%s' % znode_vlan_id)
            tagged_fabric_vn_validation_path = os.path.join(
                _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
                'fabric:%s' % fabric_uuid,
                'virtual-network:%s' % vn_uuid)
            tagged_fabric_vlan_validation_path = os.path.join(
                _DEFAULT_ZK_FABRIC_ENTERPRISE_PATH_PREFIX,
                'fabric:%s' % fabric_uuid,
                'vlan:%s' % znode_vlan_id)

        if is_untagged_vlan:
            cls.db_conn._zk_db._zk_client.delete_node(
                untagged_validation_path, True)
        else:
            if not fabric_enterprise_style:
                cls.db_conn._zk_db._zk_client.delete_node(
                    tagged_validation_path, True)
            else:
                cls.db_conn._zk_db._zk_client.delete_node(
                    tagged_vn_validation_path, True)
                cls.db_conn._zk_db._zk_client.delete_node(
                    tagged_vlan_validation_path, True)
                cls.db_conn._zk_db._zk_client.delete_node(
                    tagged_fabric_vn_validation_path, True)
                cls.db_conn._zk_db._zk_client.delete_node(
                    tagged_fabric_vlan_validation_path, True)

        return True, ''

    @classmethod
    def post_dbe_delete(cls, id, obj_dict, db_conn):
        api_server = db_conn.get_api_server()
        # For baremetal, delete the VPG object
        for vpg_back_ref in obj_dict.get('virtual_port_group_back_refs',
                                         []):
            fqname = vpg_back_ref['to']

            # Check if VPG is involved and and it's not referring to any other
            # VMI. If yes, then clean up
            vpg_uuid = db_conn.fq_name_to_uuid('virtual_port_group', fqname)
            ok, vpg_dict = db_conn.dbe_read(
                obj_type='virtual-port-group',
                obj_id=vpg_uuid,
                obj_fields=['virtual_port_group_user_created',
                            'virtual_machine_interface_refs',
                            'annotations'])
            if not ok:
                return ok, vpg_dict

            # VMI got deleted from VPG
            # Delete the associated ZNode
            ok, result = cls._delete_validations_znodes(
                vpg_dict, obj_dict, db_conn, vpg_uuid)
            if not ok:
                return ok, result

            if (not vpg_dict.get('virtual_machine_interface_refs') and
                    vpg_dict.get('virtual_port_group_user_created') is False):

                api_server.internal_request_delete('virtual_port_group',
                                                   vpg_uuid)

        return True, ""
