implementing routing for VPP
[groupbasedpolicy.git] / renderers / vpp / src / main / java / org / opendaylight / groupbasedpolicy / renderer / vpp / manager / VppNodeManager.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.vpp.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
14 import javax.annotation.Nullable;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.List;
18 import java.util.concurrent.*;
19 import java.util.stream.Collectors;
20 import com.google.common.base.Optional;
21 import com.google.common.base.Preconditions;
22 import com.google.common.util.concurrent.CheckedFuture;
23 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
24 import org.opendaylight.controller.md.sal.binding.api.MountPoint;
25 import org.opendaylight.controller.md.sal.binding.api.MountPointService;
26 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
27 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
28 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
29 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
30 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
31 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
32 import org.opendaylight.groupbasedpolicy.renderer.vpp.nat.NatUtil;
33 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.VppIidFactory;
34 import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev140508.EthernetCsmacd;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.Interface1;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.nodes.RendererNode;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.nodes.RendererNodeBuilder;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.nodes.RendererNodeKey;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.VppInterfaceAugmentation;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.VppInterfaceAugmentationBuilder;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.renderers.renderer.renderer.nodes.renderer.node.PhysicalInterface;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.renderers.renderer.renderer.nodes.renderer.node.PhysicalInterfaceBuilder;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability;
51 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
52 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
55 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
56 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
57 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
58 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import com.google.common.collect.Lists;
63 public class VppNodeManager {
64
65     private static final short DURATION = 3000;
66     private static final TopologyId TOPOLOGY_ID = new TopologyId("topology-netconf");
67     private static final Logger LOG = LoggerFactory.getLogger(VppNodeManager.class);
68     private static final String V3PO_CAPABILITY = "(urn:opendaylight:params:xml:ns:yang:v3po?revision=2016-12-14)v3po";
69     private static final String INTERFACES_CAPABILITY = "(urn:ietf:params:xml:ns:yang:ietf-interfaces?revision=2014-05-08)ietf-interfaces";
70     private static final NodeId CONTROLLER_CONFIG_NODE = new NodeId("controller-config");
71     private final DataBroker dataBroker;
72     private final List<String> requiredCapabilities;
73     private final MountPointService mountService;
74
75     public VppNodeManager(final DataBroker dataBroker, final BindingAwareBroker.ProviderContext session) {
76         this.dataBroker = Preconditions.checkNotNull(dataBroker);
77         this.mountService = Preconditions.checkNotNull(session.getSALService(MountPointService.class));
78         requiredCapabilities = initializeRequiredCapabilities();
79     }
80
81     /**
82      * Synchronizes nodes to DataStore based on their modification state which results in
83      * create/update/remove of Node.
84      */
85     public void syncNodes(Node dataAfter, Node dataBefore) {
86         if (isControllerConfigNode(dataAfter, dataBefore)) {
87             LOG.trace("{} is ignored by VPP-renderer", CONTROLLER_CONFIG_NODE);
88             return;
89         }
90         // New node
91         if (dataBefore == null && dataAfter != null) {
92             createNode(dataAfter);
93         }
94         // Connected/disconnected node
95         if (dataBefore != null && dataAfter != null) {
96             updateNode(dataAfter);
97         }
98         // Removed node
99         if (dataBefore != null && dataAfter == null) {
100             removeNode(dataBefore);
101         }
102     }
103
104     private boolean isControllerConfigNode(Node dataAfter, Node dataBefore) {
105         if (dataAfter != null) {
106             return CONTROLLER_CONFIG_NODE.equals(dataAfter.getNodeId());
107         }
108         return CONTROLLER_CONFIG_NODE.equals(dataBefore.getNodeId());
109     }
110
111     private void createNode(Node node) {
112         LOG.info("Registering new node {}", node.getNodeId().getValue());
113         NetconfNode netconfNode = getNodeAugmentation(node);
114         if (netconfNode == null) {
115             return;
116         }
117         NetconfNodeConnectionStatus.ConnectionStatus connectionStatus = netconfNode.getConnectionStatus();
118         switch (connectionStatus) {
119             case Connecting:
120                 LOG.info("Connecting device {} ...", node.getNodeId().getValue());
121                 break;
122             case Connected:
123                 resolveConnectedNode(node, netconfNode);
124                 break;
125             default:
126                 break;
127         }
128     }
129
130     private void updateNode(Node node) {
131         LOG.info("Updating node {}", node.getNodeId());
132         NetconfNode netconfNode = getNodeAugmentation(node);
133         if (netconfNode == null || netconfNode.getConnectionStatus() == null) {
134             return;
135         }
136         NetconfNodeConnectionStatus.ConnectionStatus afterNodeStatus = netconfNode.getConnectionStatus();
137         if (afterNodeStatus.equals(Connected)) {
138             resolveConnectedNode(node, netconfNode);
139         }
140         if (afterNodeStatus.equals(Connecting)) {
141             resolveDisconnectedNode(node);
142             LOG.info("Node {} is disconnected, removing from available nodes", node.getNodeId().getValue());
143         }
144     }
145
146     private void removeNode(Node node) {
147         resolveDisconnectedNode(node);
148         LOG.info("Node {} is removed", node.getNodeId().getValue());
149     }
150
151     private void resolveConnectedNode(Node node, NetconfNode netconfNode) {
152         InstanceIdentifier<Node> mountPointIid = getMountpointIid(node);
153         RendererNode rendererNode = remapNode(mountPointIid);
154         if (!isCapableNetconfDevice(node, netconfNode)) {
155             LOG.warn("Node {} is not connected.", node.getNodeId().getValue());
156             return;
157         }
158         final DataBroker mountpoint = getNodeMountPoint(mountPointIid);
159         if (mountpoint == null) {
160             LOG.warn("Mountpoint not available for node {}", node.getNodeId().getValue());
161             return;
162         }
163         final WriteTransaction wTx = dataBroker.newWriteOnlyTransaction();
164         wTx.put(LogicalDatastoreType.OPERATIONAL, VppIidFactory.getRendererNodeIid(rendererNode), rendererNode, true);
165         DataStoreHelper.submitToDs(wTx);
166         LOG.info("Node {} is capable and ready.", node.getNodeId().getValue());
167         syncPhysicalInterfacesInLocalDs(mountpoint, mountPointIid);
168     }
169
170     private void resolveDisconnectedNode(Node node) {
171         InstanceIdentifier<Node> mountPointIid = getMountpointIid(node);
172         RendererNode rendererNode = remapNode(mountPointIid);
173         final WriteTransaction wTx = dataBroker.newWriteOnlyTransaction();
174         wTx.delete(LogicalDatastoreType.OPERATIONAL, VppIidFactory.getRendererNodeIid(rendererNode));
175         CheckedFuture<Void, TransactionCommitFailedException> submitFuture = wTx.submit();
176         try {
177             submitFuture.checkedGet();
178         } catch (TransactionCommitFailedException e) {
179             LOG.error("Write transaction failed to {}", e.getMessage());
180         } catch (Exception e) {
181             LOG.error("Failed to .. {}", e.getMessage());
182         }
183     }
184
185     @Nullable
186     private DataBroker getNodeMountPoint(InstanceIdentifier<Node> mountPointIid) {
187         final Future<Optional<MountPoint>> futureOptionalObject = getMountpointFromSal(mountPointIid);
188         try {
189             final Optional<MountPoint> optionalObject = futureOptionalObject.get();
190             LOG.debug("Optional mountpoint object: {}", optionalObject);
191             MountPoint mountPoint;
192             if (optionalObject.isPresent()) {
193                 mountPoint = optionalObject.get();
194                 if (mountPoint != null) {
195                     Optional<DataBroker> optionalDataBroker = mountPoint.getService(DataBroker.class);
196                     if (optionalDataBroker.isPresent()) {
197                         return optionalDataBroker.get();
198                     } else {
199                         LOG.warn("Cannot obtain data broker from mountpoint {}", mountPoint);
200                     }
201                 } else {
202                     LOG.warn("Cannot obtain mountpoint with IID {}", mountPointIid);
203                 }
204             }
205             return null;
206         } catch (ExecutionException | InterruptedException e) {
207             LOG.warn("Unable to obtain mountpoint ... {}", e);
208             return null;
209         }
210     }
211
212     private RendererNode remapNode(InstanceIdentifier<Node> path) {
213         RendererNodeBuilder rendererNodeBuilder = new RendererNodeBuilder();
214         rendererNodeBuilder.setKey(new RendererNodeKey(path)).setNodePath(path);
215         return rendererNodeBuilder.build();
216     }
217
218     private InstanceIdentifier<Node> getMountpointIid(Node node) {
219         return InstanceIdentifier.builder(NetworkTopology.class)
220             .child(Topology.class, new TopologyKey(TOPOLOGY_ID))
221             .child(Node.class, new NodeKey(node.getNodeId()))
222             .build();
223     }
224
225     private boolean isCapableNetconfDevice(Node node, NetconfNode netconfAugmentation) {
226         if (netconfAugmentation.getAvailableCapabilities() == null
227                 || netconfAugmentation.getAvailableCapabilities().getAvailableCapability() == null
228                 || netconfAugmentation.getAvailableCapabilities().getAvailableCapability().isEmpty()) {
229             LOG.warn("Node {} does not contain any capabilities", node.getNodeId().getValue());
230             return false;
231         }
232         if (!capabilityCheck(netconfAugmentation.getAvailableCapabilities().getAvailableCapability())) {
233             LOG.warn("Node {} does not contain all capabilities required by vpp-renderer", node.getNodeId().getValue());
234             return false;
235         }
236         return true;
237     }
238
239     private boolean capabilityCheck(final List<AvailableCapability> capabilities) {
240         final List<String> availableCapabilities = capabilities.stream()
241                 .map(AvailableCapability::getCapability)
242                 .collect(Collectors.toList());
243         return requiredCapabilities.stream()
244                 .allMatch(availableCapabilities::contains);
245     }
246
247     private NetconfNode getNodeAugmentation(Node node) {
248         NetconfNode netconfNode = node.getAugmentation(NetconfNode.class);
249         if (netconfNode == null) {
250             LOG.warn("Node {} is not a netconf device", node.getNodeId().getValue());
251             return null;
252         }
253         return netconfNode;
254     }
255
256     /**
257      * Initialize all common capabilities required by VPP renderer. Any connected node is examined
258      * whether it's
259      * an appropriate device to handle configuration created by this renderer. A device must support
260      * all capabilities
261      * in list below.
262      *
263      * @return list of string representations of required capabilities
264      */
265     private List<String> initializeRequiredCapabilities() {
266         // Required device capabilities
267
268         String[] capabilityEntries = {V3PO_CAPABILITY, INTERFACES_CAPABILITY};
269         return Arrays.asList(capabilityEntries);
270     }
271
272     // TODO bug 7699
273     // This works as a workaround for mountpoint registration in cluster. If application is registered on different
274     // node as netconf service, it obtains mountpoint registered by SlaveSalFacade (instead of MasterSalFacade). However
275     // this service registers mountpoint a moment later then connectionStatus is set to "Connected". If NodeManager hits
276     // state where device is connected but mountpoint is not yet available, try to get it again in a while
277     private Future<Optional<MountPoint>> getMountpointFromSal(final InstanceIdentifier<Node> iid) {
278         final ExecutorService executorService = Executors.newSingleThreadExecutor();
279         final Callable<Optional<MountPoint>> task = () -> {
280             byte attempt = 0;
281             do {
282                 try {
283                     final Optional<MountPoint> optionalMountpoint = mountService.getMountPoint(iid);
284                     if (optionalMountpoint.isPresent()) {
285                         return optionalMountpoint;
286                     }
287                     LOG.warn("Mountpoint {} is not registered yet", iid);
288                     Thread.sleep(DURATION);
289                 } catch (InterruptedException e) {
290                     LOG.warn("Thread interrupted to ", e);
291                 }
292                 attempt ++;
293             } while (attempt <= 3);
294             return Optional.absent();
295         };
296         return executorService.submit(task);
297     }
298
299     private void syncPhysicalInterfacesInLocalDs(DataBroker mountPointDataBroker, InstanceIdentifier<Node> nodeIid) {
300         ReadWriteTransaction rwTx = dataBroker.newReadWriteTransaction();
301         InstanceIdentifier.create(Interfaces.class);
302         ReadOnlyTransaction rTx = mountPointDataBroker.newReadOnlyTransaction();
303         Optional<Interfaces> readIfaces = DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION,
304                 InstanceIdentifier.create(Interfaces.class), rTx);
305         if (readIfaces.isPresent()) {
306             InstanceIdentifier<RendererNode> rendererNodeIid = VppIidFactory.getRendererNodesIid()
307                 .builder()
308                 .child(RendererNode.class, new RendererNodeKey(nodeIid))
309                 .build();
310             Optional<RendererNode> optRendNode = DataStoreHelper.readFromDs(LogicalDatastoreType.OPERATIONAL,
311                     rendererNodeIid, rwTx);
312             RendererNode rendNode = new RendererNodeBuilder(optRendNode.get()).addAugmentation(
313                     VppInterfaceAugmentation.class, resolveTerminationPoints(readIfaces.get())).build();
314             rwTx.put(LogicalDatastoreType.OPERATIONAL, VppIidFactory.getRendererNodeIid(optRendNode.get()), rendNode,
315                     true);
316         }
317         rTx.close();
318         DataStoreHelper.submitToDs(rwTx);
319     }
320
321     private VppInterfaceAugmentation resolveTerminationPoints(Interfaces interfaces) {
322         List<PhysicalInterface> phIfaces = new ArrayList<>();
323         PhysicalInterfaceBuilder phIface = new PhysicalInterfaceBuilder();
324         if (interfaces != null && interfaces.getInterface() != null) {
325             interfaces.getInterface()
326                 .stream()
327                 .filter(iface -> iface.getType().equals(EthernetCsmacd.class))
328                 .filter(iface -> iface.getAugmentation(Interface1.class) != null)
329                 .forEach(iface -> {
330                     phIface.setInterfaceName(iface.getName());
331                     phIface.setType(iface.getType());
332                     phIface.setAddress(resolveIpAddress(iface.getAugmentation(Interface1.class)));
333                     phIfaces.add(phIface.build());
334                 });
335         }
336         return new VppInterfaceAugmentationBuilder().setPhysicalInterface(phIfaces).build();
337     }
338
339     private List<IpAddress> resolveIpAddress(Interface1 iface) {
340         if (iface.getIpv4() != null && iface.getIpv4().getAddress() != null) {
341             return iface.getIpv4().getAddress().stream().map(ipv4 -> {
342                 return new IpAddress(new Ipv4Address(ipv4.getIp().getValue()));
343             }).collect(Collectors.toList());
344         } else if (iface.getIpv6() != null && iface.getIpv6().getAddress() != null) {
345             return iface.getIpv6().getAddress().stream().map(ipv6 -> {
346                 return new IpAddress(new Ipv4Address(ipv6.getIp().getValue()));
347             }).collect(Collectors.toList());
348         }
349         return Lists.newArrayList();
350     }
351 }