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