Bug 6745 Do not skip first data for reconciliation
[openflowplugin.git] / applications / forwardingrules-sync / src / main / java / org / opendaylight / openflowplugin / applications / frsync / impl / SimplifiedOperationalListener.java
1 /**
2  * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.openflowplugin.applications.frsync.impl;
10
11 import com.google.common.base.Optional;
12 import com.google.common.util.concurrent.ListenableFuture;
13 import java.text.ParseException;
14 import java.text.SimpleDateFormat;
15 import java.util.Collection;
16 import java.util.Date;
17 import java.util.List;
18 import javax.annotation.Nonnull;
19 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
20 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType;
21 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
22 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
23 import org.opendaylight.openflowplugin.applications.frsync.SyncReactor;
24 import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeDao;
25 import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeSnapshotDao;
26 import org.opendaylight.openflowplugin.applications.frsync.impl.clustering.DeviceMastershipManager;
27 import org.opendaylight.openflowplugin.applications.frsync.util.ModificationUtil;
28 import org.opendaylight.openflowplugin.applications.frsync.util.PathUtil;
29 import org.opendaylight.openflowplugin.applications.frsync.util.ReconciliationRegistry;
30 import org.opendaylight.openflowplugin.applications.frsync.util.SyncupEntry;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableStatisticsGatheringStatus;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.snapshot.gathering.status.grouping.SnapshotGatheringStatusEnd;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
39 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * Listens to operational changes and starts reconciliation through {@link SyncReactor} when necessary.
45  */
46 public class SimplifiedOperationalListener extends AbstractFrmSyncListener<Node> {
47
48     private static final Logger LOG = LoggerFactory.getLogger(SimplifiedOperationalListener.class);
49     public static final String DATE_AND_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
50     private final SyncReactor reactor;
51     private final FlowCapableNodeSnapshotDao operationalSnapshot;
52     private final FlowCapableNodeDao configDao;
53     private final ReconciliationRegistry reconciliationRegistry;
54     private final DeviceMastershipManager deviceMastershipManager;
55
56     public SimplifiedOperationalListener(final SyncReactor reactor,
57                                          final FlowCapableNodeSnapshotDao operationalSnapshot,
58                                          final FlowCapableNodeDao configDao,
59                                          final ReconciliationRegistry reconciliationRegistry,
60                                          final DeviceMastershipManager deviceMastershipManager) {
61         this.reactor = reactor;
62         this.operationalSnapshot = operationalSnapshot;
63         this.configDao = configDao;
64         this.reconciliationRegistry = reconciliationRegistry;
65         this.deviceMastershipManager = deviceMastershipManager;
66     }
67
68     @Override
69     public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Node>> modifications) {
70         super.onDataTreeChanged(modifications);
71     }
72
73     /**
74      * Update cache, register for device mastership when device connected and start reconciliation if device
75      * is registered and actual modification is consistent.Skip the event otherwise.
76      */
77     protected Optional<ListenableFuture<Boolean>> processNodeModification(
78             final DataTreeModification<Node> modification) {
79         final NodeId nodeId = ModificationUtil.nodeId(modification);
80         updateCache(modification);
81
82         final boolean isAdd = isAdd(modification) || isAddLogical(modification);
83
84         if (isAdd) {
85             deviceMastershipManager.onDeviceConnected(nodeId);
86         }
87
88         // if node is registered for reconcile we need consistent data from operational DS (skip partial collections)
89         // but we can accept first modification since all statistics are intentionally collected in one step on startup
90         if (reconciliationRegistry.isRegistered(nodeId) && (isAdd || isConsistentForReconcile(modification))) {
91             return reconciliation(modification);
92         } else {
93             return skipModification(modification);
94         }
95     }
96
97     /**
98      * Remove if delete. Update only if FlowCapableNode Augmentation modified.
99      * Unregister for device mastership.
100      * @param modification Datastore modification
101      */
102     private void updateCache(final DataTreeModification<Node> modification) {
103         NodeId nodeId = ModificationUtil.nodeId(modification);
104         if (isDelete(modification) || isDeleteLogical(modification)) {
105             operationalSnapshot.updateCache(nodeId, Optional.absent());
106             deviceMastershipManager.onDeviceDisconnected(nodeId);
107             return;
108         }
109         operationalSnapshot.updateCache(nodeId, Optional.fromNullable(ModificationUtil.flowCapableNodeAfter(modification)));
110     }
111
112     private Optional<ListenableFuture<Boolean>> skipModification(final DataTreeModification<Node> modification) {
113         if (LOG.isTraceEnabled()) {
114             LOG.trace("Skipping operational modification: {}, before {}, after {}",
115                     ModificationUtil.nodeIdValue(modification),
116                     modification.getRootNode().getDataBefore() == null ? "null" : "nonnull",
117                     modification.getRootNode().getDataAfter() == null ? "null" : "nonnull");
118         }
119         return Optional.absent();
120     }
121
122     private boolean isDelete(final DataTreeModification<Node> modification) {
123         return ModificationType.DELETE == modification.getRootNode().getModificationType();
124     }
125
126     /**
127      * All connectors disappeared from operational store (logical delete).
128      */
129     private boolean isDeleteLogical(final DataTreeModification<Node> modification) {
130         final DataObjectModification<Node> rootNode = modification.getRootNode();
131         return !safeConnectorsEmpty(rootNode.getDataBefore()) && safeConnectorsEmpty(rootNode.getDataAfter());
132
133     }
134
135     private boolean isAdd(final DataTreeModification<Node> modification) {
136         final DataObjectModification<Node> rootNode = modification.getRootNode();
137         return rootNode.getDataBefore() == null && rootNode.getDataAfter() != null;
138     }
139
140     /**
141      * All connectors appeared in operational store (logical add).
142      */
143     private boolean isAddLogical(final DataTreeModification<Node> modification) {
144         final DataObjectModification<Node> rootNode = modification.getRootNode();
145         return safeConnectorsEmpty(rootNode.getDataBefore()) && !safeConnectorsEmpty(rootNode.getDataAfter());
146     }
147
148     /**
149      * If node is present in config DS diff between wanted configuration (in config DS) and actual device
150      * configuration (coming from operational) should be calculated and sent to device.
151      * @param modification from DS
152      * @return optional syncup future
153      */
154     private Optional<ListenableFuture<Boolean>> reconciliation(final DataTreeModification<Node> modification) {
155         final NodeId nodeId = ModificationUtil.nodeId(modification);
156         final Optional<FlowCapableNode> nodeConfiguration = configDao.loadByNodeId(nodeId);
157
158         if (nodeConfiguration.isPresent()) {
159             LOG.debug("Reconciliation {}: {}", dsType(), nodeId.getValue());
160             final InstanceIdentifier<FlowCapableNode> nodePath = InstanceIdentifier.create(Nodes.class)
161                     .child(Node.class, new NodeKey(ModificationUtil.nodeId(modification)))
162                     .augmentation(FlowCapableNode.class);
163             final FlowCapableNode fcOperationalNode = ModificationUtil.flowCapableNodeAfter(modification);
164             final SyncupEntry syncupEntry = new SyncupEntry(nodeConfiguration.get(), LogicalDatastoreType.CONFIGURATION,
165                                                             fcOperationalNode, dsType());
166             return Optional.of(reactor.syncup(nodePath, syncupEntry));
167         } else {
168             LOG.debug("Config not present for reconciliation: {}", nodeId.getValue());
169             reconciliationRegistry.unregisterIfRegistered(nodeId);
170             return skipModification(modification);
171         }
172     }
173
174     /**
175      * Check if modification is consistent for reconciliation. We need fresh data, which means that current statistics
176      * were collected after registration for reconcile and whole bunch of statistics was collected successfully.
177      * @param modification from DS
178      * @return status of modification
179      */
180     private boolean isConsistentForReconcile(final DataTreeModification<Node> modification) {
181         final NodeId nodeId = PathUtil.digNodeId(modification.getRootPath().getRootIdentifier());
182         final FlowCapableStatisticsGatheringStatus gatheringStatus = modification.getRootNode().getDataAfter()
183                 .getAugmentation(FlowCapableStatisticsGatheringStatus.class);
184
185         if (gatheringStatus == null) {
186             LOG.trace("Statistics gathering never started: {}", nodeId.getValue());
187             return false;
188         }
189
190         final SnapshotGatheringStatusEnd gatheringStatusEnd = gatheringStatus.getSnapshotGatheringStatusEnd();
191
192         if (gatheringStatusEnd == null) {
193             LOG.trace("Statistics gathering is not over yet: {}", nodeId.getValue());
194             return false;
195         }
196
197         if (!gatheringStatusEnd.isSucceeded()) {
198             LOG.trace("Statistics gathering was not successful: {}", nodeId.getValue());
199             return false;
200         }
201
202         try {
203             Date timestampOfRegistration = reconciliationRegistry.getRegistrationTimestamp(nodeId);
204             final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_AND_TIME_FORMAT);
205             Date timestampOfStatistics = simpleDateFormat.parse(gatheringStatusEnd.getEnd().getValue());
206             if (timestampOfStatistics.after(timestampOfRegistration)) {
207                 LOG.debug("Fresh operational present: {}", nodeId.getValue());
208                 return true;
209             }
210         } catch (ParseException e) {
211             LOG.warn("Timestamp parsing error {}", e);
212         }
213         LOG.debug("Fresh operational not present: {}", nodeId.getValue());
214         return false;
215     }
216
217     private static boolean safeConnectorsEmpty(final Node node) {
218         if (node == null) {
219             return true;
220         }
221         final List<NodeConnector> nodeConnectors = node.getNodeConnector();
222         return nodeConnectors == null || nodeConnectors.isEmpty();
223     }
224
225     @Override
226     public LogicalDatastoreType dsType() {
227         return LogicalDatastoreType.OPERATIONAL;
228     }
229 }