f596ed6a2ec66d50b0c91684872c52616a129398
[netconf.git] / apps / netconf-topology-singleton / src / main / java / org / opendaylight / netconf / topology / singleton / impl / NetconfNodeManager.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.netconf.topology.singleton.impl;
9
10 import akka.actor.ActorRef;
11 import akka.actor.ActorSelection;
12 import akka.actor.PoisonPill;
13 import akka.dispatch.OnComplete;
14 import akka.pattern.AskTimeoutException;
15 import akka.pattern.Patterns;
16 import akka.util.Timeout;
17 import java.util.Collection;
18 import org.checkerframework.checker.lock.qual.GuardedBy;
19 import org.checkerframework.checker.lock.qual.Holding;
20 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
21 import org.opendaylight.mdsal.binding.api.DataObjectModification;
22 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
23 import org.opendaylight.mdsal.binding.api.DataTreeModification;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
26 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
27 import org.opendaylight.netconf.topology.singleton.api.NetconfTopologySingletonService;
28 import org.opendaylight.netconf.topology.singleton.impl.actors.NetconfNodeActor;
29 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologySetup;
30 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologyUtils;
31 import org.opendaylight.netconf.topology.singleton.messages.AskForMasterMountPoint;
32 import org.opendaylight.netconf.topology.singleton.messages.RefreshSlaveActor;
33 import org.opendaylight.netconf.topology.singleton.messages.UnregisterSlaveMountPoint;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev230430.ConnectionOper.ConnectionStatus;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode;
36 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
37 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
38 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
39 import org.opendaylight.yangtools.concepts.ListenerRegistration;
40 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * Managing and reacting on data tree changes in specific netconf node when master writes status to the operational
46  * data store (e.g. handling lifecycle of slave mount point).
47  */
48 class NetconfNodeManager
49         implements ClusteredDataTreeChangeListener<Node>, NetconfTopologySingletonService, AutoCloseable {
50
51     private static final Logger LOG = LoggerFactory.getLogger(NetconfNodeManager.class);
52
53     private final Timeout actorResponseWaitTime;
54     private final DOMMountPointService mountPointService;
55
56     private volatile NetconfTopologySetup setup;
57     private volatile ListenerRegistration<NetconfNodeManager> dataChangeListenerRegistration;
58     private volatile RemoteDeviceId id;
59
60     @GuardedBy("this")
61     private ActorRef slaveActorRef;
62
63     @GuardedBy("this")
64     private boolean closed;
65
66     @GuardedBy("this")
67     private int lastUpdateCount;
68
69     NetconfNodeManager(final NetconfTopologySetup setup, final RemoteDeviceId id, final Timeout actorResponseWaitTime,
70                        final DOMMountPointService mountPointService) {
71         this.setup = setup;
72         this.id = id;
73         this.actorResponseWaitTime = actorResponseWaitTime;
74         this.mountPointService = mountPointService;
75     }
76
77     @Override
78     public void onDataTreeChanged(final Collection<DataTreeModification<Node>> changes) {
79         for (final DataTreeModification<Node> change : changes) {
80             final DataObjectModification<Node> rootNode = change.getRootNode();
81             final NodeId nodeId = NetconfTopologyUtils.getNodeId(rootNode.getIdentifier());
82             switch (rootNode.getModificationType()) {
83                 case SUBTREE_MODIFIED:
84                     LOG.debug("{}: Operational state for node {} - subtree modified from {} to {}",
85                             id, nodeId, rootNode.getDataBefore(), rootNode.getDataAfter());
86                     handleSlaveMountPoint(rootNode);
87                     break;
88                 case WRITE:
89                     if (rootNode.getDataBefore() != null) {
90                         LOG.debug("{}: Operational state for node {} updated from {} to {}",
91                                 id, nodeId, rootNode.getDataBefore(), rootNode.getDataAfter());
92                     } else {
93                         LOG.debug("{}: Operational state for node {} created: {}",
94                                 id, nodeId, rootNode.getDataAfter());
95                     }
96                     handleSlaveMountPoint(rootNode);
97                     break;
98                 case DELETE:
99                     LOG.debug("{}: Operational state for node {} deleted.", id, nodeId);
100                     unregisterSlaveMountpoint();
101                     break;
102                 default:
103                     LOG.debug("{}: Uknown operation for node: {}", id, nodeId);
104             }
105         }
106     }
107
108     @Override
109     public synchronized void close() {
110         if (closed) {
111             return;
112         }
113
114         closed = true;
115         closeActor();
116         if (dataChangeListenerRegistration != null) {
117             dataChangeListenerRegistration.close();
118             dataChangeListenerRegistration = null;
119         }
120     }
121
122     @Holding("this")
123     private void closeActor() {
124         if (slaveActorRef != null) {
125             LOG.debug("{}: Sending poison pill to {}", id, slaveActorRef);
126             slaveActorRef.tell(PoisonPill.getInstance(), ActorRef.noSender());
127             slaveActorRef = null;
128         }
129     }
130
131     private synchronized void unregisterSlaveMountpoint() {
132         lastUpdateCount++;
133         if (slaveActorRef != null) {
134             LOG.debug("{}: Sending message to unregister slave mountpoint to {}", id, slaveActorRef);
135             slaveActorRef.tell(new UnregisterSlaveMountPoint(), ActorRef.noSender());
136         }
137     }
138
139     void registerDataTreeChangeListener(final String topologyId, final NodeKey key) {
140         final InstanceIdentifier<Node> path = NetconfTopologyUtils.createTopologyNodeListPath(key, topologyId);
141         LOG.debug("{}: Registering data tree change listener on path {}", id, path);
142         dataChangeListenerRegistration = setup.getDataBroker().registerDataTreeChangeListener(
143                 DataTreeIdentifier.create(LogicalDatastoreType.OPERATIONAL, path), this);
144     }
145
146     private synchronized void handleSlaveMountPoint(final DataObjectModification<Node> rootNode) {
147         if (closed) {
148             return;
149         }
150
151         @SuppressWarnings("ConstantConditions")
152         final NetconfNode netconfNodeAfter = rootNode.getDataAfter().augmentation(NetconfNode.class);
153
154         if (ConnectionStatus.Connected == netconfNodeAfter.getConnectionStatus()) {
155             lastUpdateCount++;
156             createOrUpdateActorRef();
157
158             final String masterAddress = netconfNodeAfter.getClusteredConnectionStatus().getNetconfMasterNode();
159             final String masterActorPath = NetconfTopologyUtils.createActorPath(masterAddress,
160                     NetconfTopologyUtils.createMasterActorName(id.name(),
161                             netconfNodeAfter.getClusteredConnectionStatus().getNetconfMasterNode()));
162
163             final AskForMasterMountPoint askForMasterMountPoint = new AskForMasterMountPoint(slaveActorRef);
164             final ActorSelection masterActor = setup.getActorSystem().actorSelection(masterActorPath);
165
166             LOG.debug("{}: Sending {} message to master {}", id, askForMasterMountPoint, masterActor);
167
168             sendAskForMasterMountPointWithRetries(askForMasterMountPoint, masterActor, 1, lastUpdateCount);
169         } else {
170             unregisterSlaveMountpoint();
171         }
172     }
173
174     @Holding("this")
175     private void sendAskForMasterMountPointWithRetries(final AskForMasterMountPoint askForMasterMountPoint,
176             final ActorSelection masterActor, final int tries, final int updateCount) {
177         Patterns.ask(masterActor, askForMasterMountPoint, actorResponseWaitTime).onComplete(new OnComplete<>() {
178             @Override
179             public void onComplete(final Throwable failure, final Object response) {
180                 synchronized (this) {
181                     // Ignore the response if we were since closed or another notification update occurred.
182                     if (closed || updateCount != lastUpdateCount) {
183                         return;
184                     }
185
186                     if (failure instanceof AskTimeoutException) {
187                         if (tries <= 5 || tries % 10 == 0) {
188                             LOG.warn("{}: Failed to send message to {} - retrying...", id, masterActor, failure);
189                         }
190                         sendAskForMasterMountPointWithRetries(askForMasterMountPoint, masterActor, tries + 1,
191                             updateCount);
192                     } else if (failure != null) {
193                         LOG.error("{}: Failed to send message {} to {}. Slave mount point could not be created",
194                             id, askForMasterMountPoint, masterActor, failure);
195                     } else {
196                         LOG.debug("{}: {} message to {} succeeded", id, askForMasterMountPoint, masterActor);
197                     }
198                 }
199             }
200         }, setup.getActorSystem().dispatcher());
201     }
202
203     @Holding("this")
204     private void createOrUpdateActorRef() {
205         if (slaveActorRef == null) {
206             slaveActorRef = setup.getActorSystem().actorOf(NetconfNodeActor.props(setup, id, actorResponseWaitTime,
207                     mountPointService));
208             LOG.debug("{}: Slave actor created with name {}", id, slaveActorRef);
209         } else {
210             slaveActorRef.tell(new RefreshSlaveActor(setup, id, actorResponseWaitTime), ActorRef.noSender());
211         }
212     }
213
214     void refreshDevice(final NetconfTopologySetup netconfTopologyDeviceSetup, final RemoteDeviceId remoteDeviceId) {
215         setup = netconfTopologyDeviceSetup;
216         id = remoteDeviceId;
217     }
218 }