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