fix for netconf model change
[groupbasedpolicy.git] / renderers / ios-xe / src / main / java / org / opendaylight / groupbasedpolicy / renderer / ios_xe_provider / impl / manager / NodeManager.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.groupbasedpolicy.renderer.ios_xe_provider.impl.manager;
10
11 import static org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus.Connected;
12 import static org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus.Connecting;
13 import static org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus.UnableToConnect;
14
15 import java.util.Arrays;
16 import java.util.List;
17 import java.util.function.Function;
18
19 import javax.annotation.Nonnull;
20 import javax.annotation.Nullable;
21
22 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
23 import org.opendaylight.controller.md.sal.binding.api.MountPoint;
24 import org.opendaylight.controller.md.sal.binding.api.MountPointService;
25 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
26 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
27 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
28 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
29 import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.writer.NodeWriter;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
32 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.nodes.RendererNode;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.nodes.RendererNodeBuilder;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.nodes.RendererNodeKey;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionParameters;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.AvailableCapabilities;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapabilityBuilder;
42 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
43 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
44 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
45 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
46 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
47 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
48 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
49 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import com.google.common.base.Optional;
54 import com.google.common.base.Preconditions;
55 import com.google.common.util.concurrent.CheckedFuture;
56 import com.google.common.util.concurrent.FutureCallback;
57 import com.google.common.util.concurrent.Futures;
58 import com.google.common.util.concurrent.ListenableFuture;
59
60 public class NodeManager {
61
62     private static final TopologyId TOPOLOGY_ID = new TopologyId("topology-netconf");
63     private static final Logger LOG = LoggerFactory.getLogger(NodeManager.class);
64     private final DataBroker dataBroker;
65     private final MountPointService mountService;
66     private final List<AvailableCapability> requiredCapabilities;
67
68     public NodeManager(final DataBroker dataBroker, final BindingAwareBroker.ProviderContext session) {
69         this.dataBroker = Preconditions.checkNotNull(dataBroker);
70         mountService = Preconditions.checkNotNull(session.getSALService(MountPointService.class));
71         requiredCapabilities = new RequiredCapabilities().initializeRequiredCapabilities();
72     }
73
74     public void syncNodes(final Node dataAfter, final Node dataBefore) {
75         // New node
76         if (dataBefore == null && dataAfter != null) {
77             createNode(dataAfter);
78         }
79         // Connected/disconnected node
80         if (dataBefore != null && dataAfter != null) {
81             updateNode(dataAfter);
82         }
83         // Removed node
84         if (dataBefore != null && dataAfter == null) {
85             removeNode(dataBefore);
86         }
87     }
88
89     private void createNode(final Node node) {
90         LOG.info("Registering new node {}", node.getNodeId().getValue());
91         final NetconfNode netconfNode = getNodeAugmentation(node);
92         if (netconfNode == null) {
93             return;
94         }
95         final ConnectionStatus connectionStatus = netconfNode.getConnectionStatus();
96         switch (connectionStatus) {
97             case Connecting: {
98                 LOG.info("Connecting device {} ...", node.getNodeId().getValue());
99                 break;
100             }
101             case Connected: {
102                 resolveConnectedNode(node, netconfNode);
103                 break;
104             }
105             case UnableToConnect: {
106                 LOG.info("Unable to connect device {}", node.getNodeId().getValue());
107                 break;
108             }
109         }
110     }
111
112     /**
113      * Update previously added node. According to actual connection status, appropriate action is performed
114      *
115      * @param node to resolve
116      */
117     private void updateNode(final Node node) {
118         final NetconfNode netconfNode = getNodeAugmentation(node);
119         if (netconfNode == null || netconfNode.getConnectionStatus() == null) {
120             LOG.info("Node {} does not contain connection status", node.getNodeId().getValue());
121             return;
122         }
123         final ConnectionStatus afterNodeStatus = netconfNode.getConnectionStatus();
124         if (afterNodeStatus.equals(Connected)) {
125             resolveConnectedNode(node, netconfNode);
126         }
127         if (afterNodeStatus.equals(Connecting)) {
128             LOG.info("Node {} has been disconnected, removing from available nodes", node.getNodeId().getValue());
129             resolveDisconnectedNode(node);
130         }
131         if (afterNodeStatus.equals(UnableToConnect)) {
132             LOG.info("Unable to connect node {}, removing from available nodes", node.getNodeId().getValue());
133             resolveDisconnectedNode(node);
134         }
135     }
136
137     /**
138      * Removes previously added node. This node is also disconnected and removed from available nodes
139      *
140      * @param node to remove
141      */
142     private void removeNode(final Node node) {
143         Futures.addCallback(resolveDisconnectedNode(node), new FutureCallback<Boolean>() {
144             @Override
145             public void onSuccess(@Nullable Boolean result) {
146                 if (Boolean.TRUE.equals(result)) {
147                     LOG.info("Node {} has been removed", node.getNodeId().getValue());
148                 } else {
149                     LOG.warn("Failed to remove node {}", node.getNodeId().getValue());
150                 }
151             }
152
153             @Override
154             public void onFailure(@Nullable Throwable throwable) {
155                 LOG.warn("Exception thrown when removing node... {}", throwable);
156             }
157         });
158     }
159
160     /**
161      * Resolve node with {@link ConnectionStatus#Connected}. This node is reachable and can be added to nodes available
162      * for renderers
163      *
164      * @param node        to add to available nodes
165      * @param netconfNode node's netconf augmentation
166      */
167     private void resolveConnectedNode(final Node node, @Nonnull final NetconfNode netconfNode) {
168         final InstanceIdentifier mountPointIid = getMountpointIid(node);
169         // Mountpoint iid == path in renderer-node
170         final RendererNode rendererNode = remapNode(mountPointIid);
171         final NodeWriter nodeWriter = new NodeWriter();
172         nodeWriter.cache(rendererNode);
173         if (!isCapableNetconfDevice(node, netconfNode)) {
174             resolveDisconnectedNode(node);
175             return;
176         }
177         final IpAddress managementIpAddress = netconfNode.getHost().getIpAddress();
178         if (managementIpAddress == null) {
179             LOG.warn("Node {} does not contain management ip address", node.getNodeId().getValue());
180             resolveDisconnectedNode(node);
181             return;
182         }
183         Futures.addCallback(nodeWriter.commitToDatastore(dataBroker), new FutureCallback<Boolean>() {
184             @Override
185             public void onSuccess(@Nullable Boolean result) {
186                 if (Boolean.TRUE.equals(result)) {
187                     LOG.info("Node {} is ready, added to available nodes for IOS-XE Renderer", node.getNodeId().getValue());
188                 } else {
189                     LOG.warn("Connected node {} has not been resolved", node.getNodeId().getValue());
190                 }
191             }
192
193             @Override
194             public void onFailure(@Nullable Throwable throwable) {
195                 LOG.warn("Exception thrown when resolving node... {}", throwable);
196             }
197         });
198     }
199
200     /**
201      * Depending on action, this method is called when node is not reachable anymore. Such a node is removed from nodes
202      * available for renderers. Reasons why the node is offline can vary, therefore logging should be handled outside
203      *
204      * @param node to remove from available nodes
205      * @return true if removed, false otherwise
206      */
207     private ListenableFuture<Boolean> resolveDisconnectedNode(final Node node) {
208         final InstanceIdentifier mountPointIid = getMountpointIid(node);
209         final RendererNode rendererNode = remapNode(mountPointIid);
210         final NodeWriter nodeWriter = new NodeWriter();
211         nodeWriter.cache(rendererNode);
212         return nodeWriter.removeFromDatastore(dataBroker);
213     }
214
215     /**
216      * Node is remapped as renderer node with instance identifier. Used when reporting status for renderer manager
217      *
218      * @param path node IID
219      * @return {@link RendererNode} object with path
220      */
221     private RendererNode remapNode(final InstanceIdentifier path) {
222         final RendererNodeBuilder rendererNodeBuilder = new RendererNodeBuilder();
223         rendererNodeBuilder.setKey(new RendererNodeKey(path))
224                 .setNodePath(path);
225         return rendererNodeBuilder.build();
226     }
227
228     private InstanceIdentifier getMountpointIid(final Node node) {
229         return InstanceIdentifier.builder(NetworkTopology.class)
230                 .child(Topology.class, new TopologyKey(TOPOLOGY_ID))
231                 .child(Node.class, new NodeKey(node.getNodeId())).build();
232     }
233
234     private boolean isCapableNetconfDevice(final Node node, @Nonnull final NetconfNode netconfAugmentation) {
235         final AvailableCapabilities available = netconfAugmentation.getAvailableCapabilities();
236         if (available == null || available.getAvailableCapability() == null || available.getAvailableCapability().isEmpty()) {
237             LOG.warn("Node {} does not contain any capabilities", node.getNodeId().getValue());
238             return false;
239         }
240         if (!capabilityCheck(netconfAugmentation.getAvailableCapabilities().getAvailableCapability())) {
241             LOG.warn("Node {} does not contain all capabilities required by io-xe-renderer",
242                     node.getNodeId().getValue());
243             return false;
244         }
245         return true;
246     }
247
248     private boolean capabilityCheck(final List<AvailableCapability> capabilities) {
249         for (AvailableCapability requiredCapability : requiredCapabilities) {
250             if (!capabilities.contains(requiredCapability)) {
251                 return false;
252             }
253         }
254         return true;
255     }
256
257     @Nullable
258     DataBroker getNodeMountPoint(final InstanceIdentifier mountPointIid) {
259         if (mountPointIid == null) {
260             return null;
261         }
262         final MountPoint mountPoint = ((Function<InstanceIdentifier, MountPoint>) instanceIdentifier -> {
263             Optional<MountPoint> optionalObject = mountService.getMountPoint(mountPointIid);
264             if (optionalObject.isPresent()) {
265                 return optionalObject.get();
266             }
267             LOG.debug("Cannot obtain mountpoint with IID {}", mountPointIid);
268             return null;
269         }).apply(mountPointIid);
270         if (mountPoint == null) {
271             return null;
272         }
273         return ((Function<MountPoint, DataBroker>) mountPointParam -> {
274             Optional<DataBroker> optionalDataBroker = mountPointParam.getService(DataBroker.class);
275             if (optionalDataBroker.isPresent()) {
276                 return optionalDataBroker.get();
277             }
278             LOG.debug("Cannot obtain data broker from mountpoint {}", mountPointParam);
279             return null;
280         }).apply(mountPoint);
281     }
282
283     NodeId getNodeIdByMountpointIid(final InstanceIdentifier mountpointIid) {
284         final NodeKey identifier = (NodeKey) mountpointIid.firstKeyOf(Node.class);
285         return identifier.getNodeId();
286     }
287
288     java.util.Optional<String> getNodeManagementIpByMountPointIid(final InstanceIdentifier<?> mountpointIid) {
289         final NodeId nodeId = getNodeIdByMountpointIid(mountpointIid);
290         final InstanceIdentifier<Node> nodeIid = InstanceIdentifier.builder(NetworkTopology.class)
291                 .child(Topology.class, new TopologyKey(new TopologyId(NodeManager.TOPOLOGY_ID)))
292                 .child(Node.class, new NodeKey(nodeId))
293                 .build();
294         final ReadOnlyTransaction rTx = dataBroker.newReadOnlyTransaction();
295         final CheckedFuture<Optional<Node>, ReadFailedException> submitFuture =
296                 rTx.read(LogicalDatastoreType.CONFIGURATION, nodeIid);
297         rTx.close();
298         try {
299             Optional<Node> nodeOptional = submitFuture.checkedGet();
300             if (nodeOptional.isPresent()) {
301                 final NetconfNode netconfNode = getNodeAugmentation(nodeOptional.get());
302                 return java.util.Optional.ofNullable(netconfNode)
303                         .map(NetconfNodeConnectionParameters::getHost)
304                         .map(Host::getIpAddress)
305                         .map(IpAddress::getIpv4Address)
306                         .map(Ipv4Address::getValue);
307             }
308         } catch (ReadFailedException e) {
309             LOG.warn("Read node failed {}", nodeId, e);
310         }
311         return java.util.Optional.empty();
312     }
313
314     private NetconfNode getNodeAugmentation(final Node node) {
315         final NetconfNode netconfNode = node.getAugmentation(NetconfNode.class);
316         if (netconfNode == null) {
317             LOG.warn("Node {} is not a netconf device", node.getNodeId().getValue());
318             return null;
319         }
320         return netconfNode;
321     }
322
323     private static class RequiredCapabilities {
324
325         private static final AvailableCapability NED =
326                 new AvailableCapabilityBuilder().setCapability("(urn:ios?revision=2016-03-08)ned").build();
327         private static final AvailableCapability TAILF_COMMON = new AvailableCapabilityBuilder()
328             .setCapability("(http://tail-f.com/yang/common?revision=2015-05-22)tailf-common").build();
329         private static final AvailableCapability TAILF_CLI_EXTENSION = new AvailableCapabilityBuilder()
330             .setCapability("(http://tail-f.com/yang/common?revision=2015-03-19)tailf-cli-extensions").build();
331         private static final AvailableCapability TAILF_META_EXTENSION = new AvailableCapabilityBuilder()
332             .setCapability("(http://tail-f.com/yang/common?revision=2013-11-07)tailf-meta-extensions").build();
333         private static final AvailableCapability IETF_YANG_TYPES = new AvailableCapabilityBuilder()
334             .setCapability("(urn:ietf:params:xml:ns:yang:ietf-yang-types?revision=2013-07-15)ietf-yang-types").build();
335         private static final AvailableCapability IETF_INET_TYPES = new AvailableCapabilityBuilder()
336             .setCapability("(urn:ietf:params:xml:ns:yang:ietf-inet-types?revision=2013-07-15)ietf-inet-types").build();
337
338         /**
339          * Initialize all common capabilities required by IOS-XE renderer. Any connected node is examined whether it's
340          * an appropriate device to handle configuration created by this renderer. A device has to support all capabilities
341          * in list below.
342          *
343          * @return list of string representations of required capabilities
344          */
345         List<AvailableCapability> initializeRequiredCapabilities() {
346             final AvailableCapability capabilityEntries[] = {NED, TAILF_COMMON, TAILF_CLI_EXTENSION, TAILF_META_EXTENSION,
347                     IETF_YANG_TYPES, IETF_INET_TYPES};
348             return Arrays.asList(capabilityEntries);
349         }
350     }
351 }