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