/* * Copyright © 2017 Ericsson India Global Services Pvt Ltd. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.netvirt.elan.l2gw.listeners; import com.google.common.base.Optional; import com.google.common.collect.Sets; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Singleton; import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.genius.mdsalutil.MDSALUtil; import org.opendaylight.genius.utils.batching.ResourceBatchingManager; import org.opendaylight.genius.utils.hwvtep.HwvtepHACache; import org.opendaylight.genius.utils.hwvtep.HwvtepSouthboundUtils; import org.opendaylight.infrautils.jobcoordinator.JobCoordinator; import org.opendaylight.netvirt.elan.cache.ElanInstanceCache; import org.opendaylight.netvirt.elan.l2gw.ha.HwvtepHAUtil; import org.opendaylight.netvirt.elan.l2gw.ha.listeners.HAOpClusteredListener; import org.opendaylight.netvirt.elan.l2gw.utils.ElanL2GatewayUtils; import org.opendaylight.netvirt.elanmanager.utils.ElanL2GwCacheUtils; import org.opendaylight.netvirt.neutronvpn.api.l2gw.L2GatewayDevice; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.instances.ElanInstance; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.hwvtep.rev150901.HwvtepGlobalAugmentation; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.hwvtep.rev150901.hwvtep.global.attributes.LocalUcastMacs; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.hwvtep.rev150901.hwvtep.global.attributes.LogicalSwitches; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; @Singleton public class LocalUcastMacListener extends ChildListener implements ClusteredDataTreeChangeListener { public static final String NODE_CHECK = "physical"; private static final Predicate> IS_PS_NODE_IID = (iid) -> { return iid.firstKeyOf(Node.class).getNodeId().getValue().contains(NODE_CHECK); }; private static final Predicate> IS_NOT_HA_CHILD = (iid) -> { return !HwvtepHACache.getInstance().isHAEnabledDevice(iid) && !iid.firstKeyOf(Node.class).getNodeId().getValue().contains(HwvtepHAUtil.PHYSICALSWITCH); }; private static final Predicate> IS_HA_CHILD = (iid) -> { return HwvtepHACache.getInstance().isHAEnabledDevice(iid); }; private final ElanL2GatewayUtils elanL2GatewayUtils; private final HAOpClusteredListener haOpClusteredListener; private final JobCoordinator jobCoordinator; private final ElanInstanceCache elanInstanceCache; @Inject public LocalUcastMacListener(final DataBroker dataBroker, final HAOpClusteredListener haOpClusteredListener, final ElanL2GatewayUtils elanL2GatewayUtils, final JobCoordinator jobCoordinator, final ElanInstanceCache elanInstanceCache) { super(dataBroker, false); this.elanL2GatewayUtils = elanL2GatewayUtils; this.haOpClusteredListener = haOpClusteredListener; this.jobCoordinator = jobCoordinator; this.elanInstanceCache = elanInstanceCache; } @Override @PostConstruct public void init() throws Exception { ResourceBatchingManager.getInstance().registerDefaultBatchHandlers(this.dataBroker); super.init(); } @Override protected boolean proceed(final InstanceIdentifier parent) { return IS_NOT_HA_CHILD.test(parent); } protected String getElanName(final LocalUcastMacs mac) { return ((InstanceIdentifier) mac.getLogicalSwitchRef().getValue()) .firstKeyOf(LogicalSwitches.class).getHwvtepNodeName().getValue(); } @Override protected String getGroup(final InstanceIdentifier childIid, final LocalUcastMacs localUcastMacs) { return getElanName(localUcastMacs); } @Override protected void onUpdate(final Map> updatedMacsGrouped, final Map> deletedMacsGrouped) { updatedMacsGrouped.entrySet().forEach((entry) -> { entry.getValue().entrySet().forEach((entry2) -> { added(entry2.getKey(), entry2.getValue()); }); }); deletedMacsGrouped.entrySet().forEach((entry) -> { entry.getValue().entrySet().forEach((entry2) -> { removed(entry2.getKey(), entry2.getValue()); }); }); } public void removed(final InstanceIdentifier identifier, final LocalUcastMacs macRemoved) { String hwvtepNodeId = identifier.firstKeyOf(Node.class).getNodeId().getValue(); String macAddress = macRemoved.getMacEntryKey().getValue().toLowerCase(Locale.getDefault()); LOG.trace("LocalUcastMacs {} removed from {}", macAddress, hwvtepNodeId); ResourceBatchingManager.getInstance().delete(ResourceBatchingManager.ShardResource.CONFIG_TOPOLOGY, identifier); String elanName = getElanName(macRemoved); jobCoordinator.enqueueJob(elanName + HwvtepHAUtil.L2GW_JOB_KEY , () -> { L2GatewayDevice elanL2GwDevice = ElanL2GwCacheUtils.getL2GatewayDeviceFromCache(elanName, hwvtepNodeId); if (elanL2GwDevice == null) { LOG.warn("Could not find L2GatewayDevice for ELAN: {}, nodeID:{} from cache", elanName, hwvtepNodeId); return null; } elanL2GwDevice.removeUcastLocalMac(macRemoved); ElanInstance elanInstance = elanInstanceCache.get(elanName).orNull(); elanL2GatewayUtils.unInstallL2GwUcastMacFromL2gwDevices(elanName, elanL2GwDevice, Collections.singletonList(new MacAddress(macAddress.toLowerCase(Locale.getDefault())))); elanL2GatewayUtils.unInstallL2GwUcastMacFromElanDpns(elanInstance, elanL2GwDevice, Collections.singletonList(new MacAddress(macAddress.toLowerCase(Locale.getDefault())))); return null; }); } public void added(final InstanceIdentifier identifier, final LocalUcastMacs macAdded) { ResourceBatchingManager.getInstance().put(ResourceBatchingManager.ShardResource.CONFIG_TOPOLOGY, identifier, macAdded); String hwvtepNodeId = identifier.firstKeyOf(Node.class).getNodeId().getValue(); String macAddress = macAdded.getMacEntryKey().getValue().toLowerCase(Locale.getDefault()); String elanName = getElanName(macAdded); LOG.trace("LocalUcastMacs {} added to {}", macAddress, hwvtepNodeId); ElanInstance elan = elanInstanceCache.get(elanName).orNull(); if (elan == null) { LOG.warn("Could not find ELAN for mac {} being added", macAddress); return; } jobCoordinator.enqueueJob(elanName + HwvtepHAUtil.L2GW_JOB_KEY, () -> { L2GatewayDevice elanL2GwDevice = ElanL2GwCacheUtils.getL2GatewayDeviceFromCache(elanName, hwvtepNodeId); if (elanL2GwDevice == null) { LOG.warn("Could not find L2GatewayDevice for ELAN: {}, nodeID:{} from cache", elanName, hwvtepNodeId); return null; } elanL2GwDevice.addUcastLocalMac(macAdded); elanL2GatewayUtils.installL2GwUcastMacInElan(elan, elanL2GwDevice, macAddress.toLowerCase(), macAdded, null); return null; }); } @Override protected Map, DataObjectModification> getChildMod( final InstanceIdentifier parentIid, final DataObjectModification mod) { Map, DataObjectModification> result = new HashMap<>(); DataObjectModification aug = mod.getModifiedAugmentation( HwvtepGlobalAugmentation.class); if (aug != null && getModificationType(aug) != null) { Collection> children = aug.getModifiedChildren(); children.stream() .filter(childMod -> getModificationType(childMod) != null) .filter(childMod -> childMod.getDataType() == LocalUcastMacs.class) .forEach(childMod -> { LocalUcastMacs afterMac = (LocalUcastMacs) childMod.getDataAfter(); LocalUcastMacs mac = afterMac != null ? afterMac : (LocalUcastMacs)childMod.getDataBefore(); InstanceIdentifier iid = parentIid .augmentation(HwvtepGlobalAugmentation.class) .child(LocalUcastMacs.class, mac.getKey()); result.put(iid, (DataObjectModification) childMod); }); } return result; } @Override protected void onParentAdded(final DataTreeModification modification) { InstanceIdentifier nodeIid = modification.getRootPath().getRootIdentifier(); if (IS_PS_NODE_IID.test(nodeIid)) { return; } ReadWriteTransaction tx = dataBroker.newReadWriteTransaction(); haOpClusteredListener.onGlobalNodeAdd(nodeIid, modification.getRootNode().getDataAfter(), tx); tx.submit(); if (IS_HA_CHILD.test(nodeIid)) { return; } LOG.trace("On parent add {}", nodeIid); Node operNode = modification.getRootNode().getDataAfter(); Optional configNode = MDSALUtil.read(dataBroker, LogicalDatastoreType.CONFIGURATION, nodeIid); Set configMacs = getMacs(configNode); Set operMacs = getMacs(Optional.of(operNode)); Set staleMacs = Sets.difference(configMacs, operMacs); staleMacs.forEach(staleMac -> removed(getMacIid(nodeIid, staleMac), staleMac)); } InstanceIdentifier getMacIid(InstanceIdentifier nodeIid, LocalUcastMacs mac) { return nodeIid.augmentation(HwvtepGlobalAugmentation.class) .child(LocalUcastMacs.class, mac.getKey()); } Set getMacs(Optional node) { if (node.isPresent()) { HwvtepGlobalAugmentation augmentation = node.get().getAugmentation(HwvtepGlobalAugmentation.class); if (augmentation != null && augmentation.getLocalUcastMacs() != null) { return new HashSet<>(augmentation.getLocalUcastMacs()); } } return Collections.emptySet(); } @Override protected void onParentRemoved(InstanceIdentifier parent) { if (IS_PS_NODE_IID.test(parent)) { return; } LOG.trace("on parent removed {}", parent); } @Override protected InstanceIdentifier getParentWildCardPath() { return HwvtepSouthboundUtils.createHwvtepTopologyInstanceIdentifier() .child(Node.class); } }