Merge "Bug 6380 lldp-speaker - DTCL instead of DTL"
[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 org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
19 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType;
20 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
21 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
22 import org.opendaylight.openflowplugin.applications.frsync.SyncReactor;
23 import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeDao;
24 import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeSnapshotDao;
25 import org.opendaylight.openflowplugin.applications.frsync.impl.clustering.DeviceMastershipManager;
26 import org.opendaylight.openflowplugin.applications.frsync.util.ModificationUtil;
27 import org.opendaylight.openflowplugin.applications.frsync.util.PathUtil;
28 import org.opendaylight.openflowplugin.applications.frsync.util.ReconciliationRegistry;
29 import org.opendaylight.openflowplugin.applications.frsync.util.SyncupEntry;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableStatisticsGatheringStatus;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.snapshot.gathering.status.grouping.SnapshotGatheringStatusEnd;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * Listens to operational changes and starts reconciliation through {@link SyncReactor} when necessary.
44  */
45 public class SimplifiedOperationalListener extends AbstractFrmSyncListener<Node> {
46     private static final Logger LOG = LoggerFactory.getLogger(SimplifiedOperationalListener.class);
47     public static final String DATE_AND_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
48
49     private final SyncReactor reactor;
50     private final FlowCapableNodeSnapshotDao operationalSnapshot;
51     private final FlowCapableNodeDao configDao;
52     private final ReconciliationRegistry reconciliationRegistry;
53     private final DeviceMastershipManager deviceMastershipManager;
54
55     public SimplifiedOperationalListener(final SyncReactor reactor,
56                                          final FlowCapableNodeSnapshotDao operationalSnapshot,
57                                          final FlowCapableNodeDao configDao,
58                                          final ReconciliationRegistry reconciliationRegistry,
59                                          final DeviceMastershipManager deviceMastershipManager) {
60         this.reactor = reactor;
61         this.operationalSnapshot = operationalSnapshot;
62         this.configDao = configDao;
63         this.reconciliationRegistry = reconciliationRegistry;
64         this.deviceMastershipManager = deviceMastershipManager;
65     }
66
67     @Override
68     public void onDataTreeChanged(Collection<DataTreeModification<Node>> modifications) {
69         LOG.trace("Operational changes: {}", modifications.size());
70         super.onDataTreeChanged(modifications);
71     }
72
73     /**
74      * Update cache, register for device masterhip when device connected and start reconciliation if device
75      * is registered and actual modification is consistent.Skip the event otherwise.
76      * @throws InterruptedException from syncup
77      */
78     protected Optional<ListenableFuture<Boolean>> processNodeModification(
79             DataTreeModification<Node> modification) throws InterruptedException {
80         final NodeId nodeId = ModificationUtil.nodeId(modification);
81         updateCache(modification);
82
83         if (isAdd(modification) || isAddLogical(modification)) {
84             deviceMastershipManager.onDeviceConnected(nodeId);
85         }
86
87         if (reconciliationRegistry.isRegistered(nodeId) && isConsistentForReconcile(modification)) {
88             return reconciliation(modification);
89         } else {
90             return skipModification(modification);
91         }
92     }
93
94     /**
95      * Remove if delete. Update only if FlowCapableNode Augmentation modified.
96      * Unregister for device mastership.
97      * @param modification Datastore modification
98      */
99     private void updateCache(DataTreeModification<Node> modification) {
100         NodeId nodeId = ModificationUtil.nodeId(modification);
101         if (isDelete(modification) || isDeleteLogical(modification)) {
102             operationalSnapshot.updateCache(nodeId, Optional.absent());
103             deviceMastershipManager.onDeviceDisconnected(nodeId);
104             return;
105         }
106         operationalSnapshot.updateCache(nodeId, Optional.fromNullable(ModificationUtil.flowCapableNodeAfter(modification)));
107     }
108
109     private Optional<ListenableFuture<Boolean>> skipModification(DataTreeModification<Node> modification) {
110         LOG.trace("Skipping operational modification: {}, before {}, after {}",
111                 ModificationUtil.nodeIdValue(modification),
112                 modification.getRootNode().getDataBefore() == null ? "null" : "nonnull",
113                 modification.getRootNode().getDataAfter() == null ? "null" : "nonnull");
114         return Optional.absent();
115     }
116
117     /**
118      * ModificationType.DELETE.
119      */
120     private boolean isDelete(DataTreeModification<Node> modification) {
121         if (ModificationType.DELETE == modification.getRootNode().getModificationType()) {
122             LOG.trace("Delete {} (physical)", ModificationUtil.nodeIdValue(modification));
123             return true;
124         }
125
126         return false;
127     }
128
129     /**
130      * All connectors disappeared from operational store (logical delete).
131      */
132     private boolean isDeleteLogical(DataTreeModification<Node> modification) {
133         final DataObjectModification<Node> rootNode = modification.getRootNode();
134         if (!safeConnectorsEmpty(rootNode.getDataBefore()) && safeConnectorsEmpty(rootNode.getDataAfter())) {
135             LOG.trace("Delete {} (logical)", ModificationUtil.nodeIdValue(modification));
136             return true;
137         }
138
139         return false;
140     }
141
142     private boolean isAdd(DataTreeModification<Node> modification) {
143         final DataObjectModification<Node> rootNode = modification.getRootNode();
144         final Node dataAfter = rootNode.getDataAfter();
145         final Node dataBefore = rootNode.getDataBefore();
146
147         final boolean nodeAppearedInOperational = dataBefore == null && dataAfter != null;
148         if (nodeAppearedInOperational) {
149             LOG.trace("Add {} (physical)", ModificationUtil.nodeIdValue(modification));
150         }
151         return nodeAppearedInOperational;
152     }
153
154     /**
155      * All connectors appeared in operational store (logical add).
156      */
157     private boolean isAddLogical(DataTreeModification<Node> modification) {
158         final DataObjectModification<Node> rootNode = modification.getRootNode();
159         if (safeConnectorsEmpty(rootNode.getDataBefore()) && !safeConnectorsEmpty(rootNode.getDataAfter())) {
160             LOG.trace("Add {} (logical)", ModificationUtil.nodeIdValue(modification));
161             return true;
162         }
163
164         return false;
165     }
166
167     /**
168      * If node is present in config DS diff between wanted configuration (in config DS) and actual device
169      * configuration (coming from operational) should be calculated and sent to device.
170      * @param modification from DS
171      * @return optional syncup future
172      * @throws InterruptedException from syncup
173      */
174     private Optional<ListenableFuture<Boolean>> reconciliation(DataTreeModification<Node> modification) throws InterruptedException {
175         final NodeId nodeId = ModificationUtil.nodeId(modification);
176         final Optional<FlowCapableNode> nodeConfiguration = configDao.loadByNodeId(nodeId);
177
178         if (nodeConfiguration.isPresent()) {
179             LOG.debug("Reconciliation: {}", nodeId.getValue());
180             final InstanceIdentifier<FlowCapableNode> nodePath = InstanceIdentifier.create(Nodes.class)
181                     .child(Node.class, new NodeKey(ModificationUtil.nodeId(modification)))
182                     .augmentation(FlowCapableNode.class);
183             final FlowCapableNode fcOperationalNode = ModificationUtil.flowCapableNodeAfter(modification);
184             final SyncupEntry syncupEntry = new SyncupEntry(nodeConfiguration.get(), LogicalDatastoreType.CONFIGURATION,
185                                                             fcOperationalNode, dsType());
186             return Optional.of(reactor.syncup(nodePath, syncupEntry));
187         } else {
188             LOG.debug("Config not present for reconciliation: {}", nodeId.getValue());
189             return skipModification(modification);
190         }
191     }
192
193     private boolean isConsistentForReconcile(DataTreeModification<Node> modification) {
194         final NodeId nodeId = PathUtil.digNodeId(modification.getRootPath().getRootIdentifier());
195         final FlowCapableStatisticsGatheringStatus gatheringStatus = modification.getRootNode().getDataAfter()
196                 .getAugmentation(FlowCapableStatisticsGatheringStatus.class);
197
198         if (gatheringStatus == null) {
199             LOG.trace("Statistics gathering never started: {}", nodeId.getValue());
200             return false;
201         }
202
203         final SnapshotGatheringStatusEnd gatheringStatusEnd = gatheringStatus.getSnapshotGatheringStatusEnd();
204
205         if (gatheringStatusEnd == null) {
206             LOG.trace("Statistics gathering is not over yet: {}", nodeId.getValue());
207             return false;
208         }
209
210         if (!gatheringStatusEnd.isSucceeded()) {
211             LOG.trace("Statistics gathering was not successful: {}", nodeId.getValue());
212             return false;
213         }
214
215         try {
216             Date timestampOfRegistration = reconciliationRegistry.getRegistrationTimestamp(nodeId);
217             final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_AND_TIME_FORMAT);
218             Date timestampOfStatistics = simpleDateFormat.parse(gatheringStatusEnd.getEnd().getValue());
219             if (timestampOfStatistics.after(timestampOfRegistration)) {
220                 LOG.debug("Fresh operational present: {}", nodeId.getValue());
221                 return true;
222             }
223         } catch (ParseException e) {
224             LOG.error("Timestamp parsing error {}", e);
225         }
226         LOG.debug("Fresh operational not present: {}", nodeId.getValue());
227         return false;
228     }
229
230     private static boolean safeConnectorsEmpty(Node node) {
231         if (node == null) {
232             return true;
233         }
234         final List<NodeConnector> nodeConnectors = node.getNodeConnector();
235         return nodeConnectors == null || nodeConnectors.isEmpty();
236     }
237
238     @Override
239     public LogicalDatastoreType dsType() {
240         return LogicalDatastoreType.OPERATIONAL;
241     }
242
243 }