/** * Copyright (c) 2016 Cisco Systems, Inc. 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.openflowplugin.applications.frsync.impl; import com.google.common.base.Optional; import com.google.common.util.concurrent.ListenableFuture; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.openflowplugin.applications.frsync.SyncReactor; import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeDao; import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeSnapshotDao; import org.opendaylight.openflowplugin.applications.frsync.impl.clustering.DeviceMastershipManager; import org.opendaylight.openflowplugin.applications.frsync.util.ModificationUtil; import org.opendaylight.openflowplugin.applications.frsync.util.PathUtil; import org.opendaylight.openflowplugin.applications.frsync.util.ReconciliationRegistry; import org.opendaylight.openflowplugin.applications.frsync.util.SyncupEntry; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableStatisticsGatheringStatus; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.snapshot.gathering.status.grouping.SnapshotGatheringStatusEnd; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Listens to operational changes and starts reconciliation through {@link SyncReactor} when necessary. */ public class SimplifiedOperationalListener extends AbstractFrmSyncListener<Node> { private static final Logger LOG = LoggerFactory.getLogger(SimplifiedOperationalListener.class); public static final String DATE_AND_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; private final SyncReactor reactor; private final FlowCapableNodeSnapshotDao operationalSnapshot; private final FlowCapableNodeDao configDao; private final ReconciliationRegistry reconciliationRegistry; private final DeviceMastershipManager deviceMastershipManager; public SimplifiedOperationalListener(final SyncReactor reactor, final FlowCapableNodeSnapshotDao operationalSnapshot, final FlowCapableNodeDao configDao, final ReconciliationRegistry reconciliationRegistry, final DeviceMastershipManager deviceMastershipManager) { this.reactor = reactor; this.operationalSnapshot = operationalSnapshot; this.configDao = configDao; this.reconciliationRegistry = reconciliationRegistry; this.deviceMastershipManager = deviceMastershipManager; } @Override public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Node>> modifications) { super.onDataTreeChanged(modifications); } /** * Update cache, register for device mastership when device connected and start reconciliation if device * is registered and actual modification is consistent.Skip the event otherwise. */ protected Optional<ListenableFuture<Boolean>> processNodeModification( final DataTreeModification<Node> modification) { Optional<ListenableFuture<Boolean>> result; final NodeId nodeId = ModificationUtil.nodeId(modification); final DataObjectModification<Node> nodeModification = modification.getRootNode(); if (isDelete(nodeModification) || isDeleteLogical(nodeModification)) { operationalSnapshot.updateCache(nodeId, Optional.absent()); deviceMastershipManager.onDeviceDisconnected(nodeId); result = skipModification(modification); } else { operationalSnapshot.updateCache(nodeId, Optional.fromNullable(ModificationUtil.flowCapableNodeAfter(modification))); final boolean isAdd = isAdd(nodeModification) || isAddLogical(nodeModification); if (isAdd) { deviceMastershipManager.onDeviceConnected(nodeId); } // if node is registered for reconcile we need consistent data from operational DS (skip partial collections) // but we can accept first modification since all statistics are intentionally collected in one step on startup if (reconciliationRegistry.isRegistered(nodeId) && (isAdd || isConsistentForReconcile(modification))) { result = reconciliation(modification); } else { result = skipModification(modification); } } return result; } private Optional<ListenableFuture<Boolean>> skipModification(final DataTreeModification<Node> modification) { if (LOG.isTraceEnabled()) { LOG.trace("Skipping operational modification: {}, before {}, after {}", ModificationUtil.nodeIdValue(modification), modification.getRootNode().getDataBefore() == null ? "null" : "nonnull", modification.getRootNode().getDataAfter() == null ? "null" : "nonnull"); } return Optional.absent(); } private boolean isDelete(final DataObjectModification<Node> nodeModification) { return Objects.nonNull(nodeModification.getDataBefore()) && Objects.isNull(nodeModification.getDataAfter()); } /** * All connectors disappeared from operational store (logical delete). */ private boolean isDeleteLogical(final DataObjectModification<Node> nodeModification) { return !safeConnectorsEmpty(nodeModification.getDataBefore()) && safeConnectorsEmpty(nodeModification.getDataAfter()); } private boolean isAdd(final DataObjectModification<Node> nodeModification) { return Objects.isNull(nodeModification.getDataBefore()) && Objects.nonNull(nodeModification.getDataAfter()); } /** * All connectors appeared in operational store (logical add). */ private boolean isAddLogical(final DataObjectModification<Node> nodeModification) { return safeConnectorsEmpty(nodeModification.getDataBefore()) && !safeConnectorsEmpty(nodeModification.getDataAfter()); } /** * If node is present in config DS diff between wanted configuration (in config DS) and actual device * configuration (coming from operational) should be calculated and sent to device. * @param modification from DS * @return optional syncup future */ private Optional<ListenableFuture<Boolean>> reconciliation(final DataTreeModification<Node> modification) { final NodeId nodeId = ModificationUtil.nodeId(modification); final Optional<FlowCapableNode> nodeConfiguration = configDao.loadByNodeId(nodeId); if (nodeConfiguration.isPresent()) { LOG.debug("Reconciliation {}: {}", dsType(), nodeId.getValue()); final InstanceIdentifier<FlowCapableNode> nodePath = InstanceIdentifier.create(Nodes.class) .child(Node.class, new NodeKey(ModificationUtil.nodeId(modification))) .augmentation(FlowCapableNode.class); final FlowCapableNode fcOperationalNode = ModificationUtil.flowCapableNodeAfter(modification); final SyncupEntry syncupEntry = new SyncupEntry(nodeConfiguration.get(), LogicalDatastoreType.CONFIGURATION, fcOperationalNode, dsType()); return Optional.of(reactor.syncup(nodePath, syncupEntry)); } else { LOG.debug("Config not present for reconciliation: {}", nodeId.getValue()); reconciliationRegistry.unregisterIfRegistered(nodeId); return skipModification(modification); } } /** * Check if modification is consistent for reconciliation. We need fresh data, which means that current statistics * were collected after registration for reconcile and whole bunch of statistics was collected successfully. * @param modification from DS * @return status of modification */ private boolean isConsistentForReconcile(final DataTreeModification<Node> modification) { final NodeId nodeId = PathUtil.digNodeId(modification.getRootPath().getRootIdentifier()); final FlowCapableStatisticsGatheringStatus gatheringStatus = modification.getRootNode().getDataAfter() .getAugmentation(FlowCapableStatisticsGatheringStatus.class); if (gatheringStatus == null) { LOG.trace("Statistics gathering never started: {}", nodeId.getValue()); return false; } final SnapshotGatheringStatusEnd gatheringStatusEnd = gatheringStatus.getSnapshotGatheringStatusEnd(); if (gatheringStatusEnd == null) { LOG.trace("Statistics gathering is not over yet: {}", nodeId.getValue()); return false; } if (!gatheringStatusEnd.isSucceeded()) { LOG.trace("Statistics gathering was not successful: {}", nodeId.getValue()); return false; } try { Date timestampOfRegistration = reconciliationRegistry.getRegistrationTimestamp(nodeId); final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_AND_TIME_FORMAT); Date timestampOfStatistics = simpleDateFormat.parse(gatheringStatusEnd.getEnd().getValue()); if (timestampOfStatistics.after(timestampOfRegistration)) { LOG.debug("Fresh operational present: {}", nodeId.getValue()); return true; } } catch (ParseException e) { LOG.warn("Timestamp parsing error {}", e); } LOG.debug("Fresh operational not present: {}", nodeId.getValue()); return false; } private static boolean safeConnectorsEmpty(final Node node) { if (node == null) { return true; } final List<NodeConnector> nodeConnectors = node.getNodeConnector(); return nodeConnectors == null || nodeConnectors.isEmpty(); } @Override public LogicalDatastoreType dsType() { return LogicalDatastoreType.OPERATIONAL; } }