3cda5aecd6ed80490a39f458161db351d072e00a
[groupbasedpolicy.git] / renderers / vpp / src / main / java / org / opendaylight / groupbasedpolicy / renderer / vpp / iface / InterfaceManager.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.iface;
10
11 import java.util.concurrent.ExecutorService;
12
13 import javax.annotation.Nonnull;
14 import javax.annotation.Nullable;
15
16 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
17 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
18 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
19 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
20 import org.opendaylight.groupbasedpolicy.renderer.vpp.commands.ConfigCommand;
21 import org.opendaylight.groupbasedpolicy.renderer.vpp.commands.VhostUserCommand;
22 import org.opendaylight.groupbasedpolicy.renderer.vpp.commands.VhostUserCommand.VhostUserCommandBuilder;
23 import org.opendaylight.groupbasedpolicy.renderer.vpp.event.NodeOperEvent;
24 import org.opendaylight.groupbasedpolicy.renderer.vpp.event.VppEndpointConfEvent;
25 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.General.Operations;
26 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.MountedDataBrokerProvider;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.absolute.location.absolute.location.LocationType;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.absolute.location.absolute.location.location.type.ExternalLocationCase;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.absolute.location.absolute.location.location.type.ExternalLocationCaseBuilder;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.endpoints.AddressEndpointWithLocation;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpoint;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.vpp.endpoint.InterfaceTypeChoice;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.vpp.endpoint._interface.type.choice.VhostUserCase;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VhostUserRole;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceAugmentation;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.L2;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.L2Builder;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.l2.base.attributes.Interconnection;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.l2.base.attributes.interconnection.BridgeBased;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.l2.base.attributes.interconnection.BridgeBasedBuilder;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import com.google.common.base.Function;
47 import com.google.common.base.Optional;
48 import com.google.common.base.Preconditions;
49 import com.google.common.base.Strings;
50 import com.google.common.eventbus.Subscribe;
51 import com.google.common.util.concurrent.AsyncFunction;
52 import com.google.common.util.concurrent.CheckedFuture;
53 import com.google.common.util.concurrent.FutureCallback;
54 import com.google.common.util.concurrent.Futures;
55 import com.google.common.util.concurrent.ListenableFuture;
56
57 public class InterfaceManager implements AutoCloseable {
58
59     private static final Logger LOG = LoggerFactory.getLogger(InterfaceManager.class);
60     private final MountedDataBrokerProvider mountDataProvider;
61     private final VppEndpointLocationProvider vppEndpointLocationProvider;
62     private final ExecutorService netconfWorker;
63
64     public InterfaceManager(@Nonnull MountedDataBrokerProvider mountDataProvider, @Nonnull DataBroker dataProvider, @Nonnull ExecutorService netconfWorker) {
65         this.mountDataProvider = Preconditions.checkNotNull(mountDataProvider);
66         this.netconfWorker = Preconditions.checkNotNull(netconfWorker);
67         this.vppEndpointLocationProvider = new VppEndpointLocationProvider(dataProvider);
68     }
69
70     @Subscribe
71     public synchronized void vppEndpointChanged(VppEndpointConfEvent event) {
72         switch (event.getDtoModificationType()) {
73             case CREATED:
74                 vppEndpointCreated(event.getAfter().get());
75                 break;
76             case UPDATED:
77                 vppEndpointDeleted(event.getBefore().get());
78                 vppEndpointCreated(event.getAfter().get());
79                 break;
80             case DELETED:
81                 vppEndpointDeleted(event.getBefore().get());
82                 break;
83         }
84     }
85
86     private void vppEndpointCreated(VppEndpoint vppEndpoint) {
87         Optional<ConfigCommand> potentialIfaceCommand = createInterfaceWithoutBdCommand(vppEndpoint, Operations.PUT);
88         if (!potentialIfaceCommand.isPresent()) {
89             return;
90         }
91         ConfigCommand ifaceWithoutBdCommand = potentialIfaceCommand.get();
92         InstanceIdentifier<?> vppNodeIid = vppEndpoint.getVppNodePath();
93         Optional<DataBroker> potentialVppDataProvider = mountDataProvider.getDataBrokerForMountPoint(vppNodeIid);
94         if (!potentialVppDataProvider.isPresent()) {
95             LOG.debug("Cannot get data broker for mount point {}", vppNodeIid);
96             return;
97         }
98         DataBroker vppDataBroker = potentialVppDataProvider.get();
99         createIfaceOnVpp(ifaceWithoutBdCommand, vppDataBroker, vppEndpoint, vppNodeIid);
100     }
101
102     private void createIfaceOnVpp(ConfigCommand createIfaceWithoutBdCommand, DataBroker vppDataBroker,
103             VppEndpoint vppEndpoint, InstanceIdentifier<?> vppNodeIid) {
104         ReadWriteTransaction rwTx = vppDataBroker.newReadWriteTransaction();
105         createIfaceWithoutBdCommand.execute(rwTx);
106         Futures.addCallback(rwTx.submit(), new FutureCallback<Void>() {
107
108             @Override
109             public void onSuccess(Void result) {
110                 LOG.debug("Create interface on VPP command was successful:\nVPP: {}\nCommand: {}", vppNodeIid,
111                         createIfaceWithoutBdCommand);
112                 vppEndpointLocationProvider.createLocationForVppEndpoint(vppEndpoint);
113             }
114
115             @Override
116             public void onFailure(Throwable t) {
117                 LOG.error("Create interface on VPP command was NOT successful:\nVPP: {}\nCommand: {}", vppNodeIid,
118                         createIfaceWithoutBdCommand, t);
119             }
120         }, netconfWorker);
121     }
122
123     private void vppEndpointDeleted(VppEndpoint vppEndpoint) {
124         Optional<ConfigCommand> potentialIfaceCommand = createInterfaceWithoutBdCommand(vppEndpoint, Operations.DELETE);
125         if (!potentialIfaceCommand.isPresent()) {
126             return;
127         }
128         ConfigCommand ifaceWithoutBdCommand = potentialIfaceCommand.get();
129         InstanceIdentifier<?> vppNodeIid = vppEndpoint.getVppNodePath();
130         Optional<DataBroker> potentialVppDataProvider = mountDataProvider.getDataBrokerForMountPoint(vppNodeIid);
131         if (!potentialVppDataProvider.isPresent()) {
132             LOG.debug("Cannot get data broker for mount point {}", vppNodeIid);
133             return;
134         }
135         DataBroker vppDataBroker = potentialVppDataProvider.get();
136         deleteIfaceOnVpp(ifaceWithoutBdCommand, vppDataBroker, vppEndpoint, vppNodeIid);
137     }
138
139     private void deleteIfaceOnVpp(ConfigCommand deleteIfaceWithoutBdCommand, DataBroker vppDataBroker,
140             VppEndpoint vppEndpoint, InstanceIdentifier<?> vppNodeIid) {
141         ReadWriteTransaction rwTx = vppDataBroker.newReadWriteTransaction();
142         deleteIfaceWithoutBdCommand.execute(rwTx);
143         Futures.addCallback(rwTx.submit(), new FutureCallback<Void>() {
144
145             @Override
146             public void onSuccess(Void result) {
147                 LOG.debug("Delete interface on VPP command was successful:\nVPP: {}\nCommand: {}", vppNodeIid,
148                         deleteIfaceWithoutBdCommand);
149                 vppEndpointLocationProvider.deleteLocationForVppEndpoint(vppEndpoint);
150             }
151
152             @Override
153             public void onFailure(Throwable t) {
154                 LOG.error("Delete interface on VPP command was NOT successful:\nVPP: {}\nCommand: {}", vppNodeIid,
155                         deleteIfaceWithoutBdCommand, t);
156             }
157         }, netconfWorker);
158     }
159
160     @Subscribe
161     public synchronized void vppNodeChanged(NodeOperEvent event) {
162         switch (event.getDtoModificationType()) {
163             case CREATED:
164                 if (event.isAfterConnected()) {
165                     // TODO read VppEndpoints or cache them during vppEndpointChanged()
166                 }
167                 break;
168             case UPDATED:
169                 if (!event.isBeforeConnected() && event.isAfterConnected()) {
170                     // TODO reconciliation - diff between disconnected snapshot and current snapshot
171                 }
172                 break;
173             case DELETED:
174                 if (event.isBeforeConnected()) {
175                     // TODO we could do snapshot of VppEndpoints 
176                     // which can be used for reconciliation
177                 }
178                 break;
179         }
180     }
181
182     private static Optional<ConfigCommand> createInterfaceWithoutBdCommand(@Nonnull VppEndpoint vppEp,
183             @Nonnull Operations operations) {
184         if (!hasNodeAndInterface(vppEp)) {
185             LOG.debug("Interface command is not created for {}", vppEp);
186             return Optional.absent();
187         }
188         VhostUserCommandBuilder builder = VhostUserCommand.builder();
189         builder.setName(vppEp.getVppInterfaceName());
190         InterfaceTypeChoice interfaceTypeChoice = vppEp.getInterfaceTypeChoice();
191         if (interfaceTypeChoice instanceof VhostUserCase) {
192             VhostUserCase vhostUserIface = (VhostUserCase) interfaceTypeChoice;
193             String socket = vhostUserIface.getSocket();
194             if (Strings.isNullOrEmpty(socket)) {
195                 LOG.debug("Vhost user interface command is not created because socket is missing. {}", vppEp);
196                 return Optional.absent();
197             }
198             builder.setSocket(socket);
199             builder.setRole(VhostUserRole.Client);
200         }
201         VhostUserCommand vhostUserCommand =
202                 builder.setOperation(operations).setDescription(vppEp.getDescription()).build();
203         return Optional.of(vhostUserCommand);
204     }
205
206     /**
207      * Adds bridge domain to an interface if the interface exist.<br>
208      * It rewrites bridge domain in case it already exist.<br>
209      * {@link VppEndpointLocationProvider#VPP_ENDPOINT_LOCATION_PROVIDER} will update location
210      * when the interface is created successfully.<br>
211      * If the interface does not exist or other problems occur {@link ListenableFuture} will fail
212      * as {@link Futures#immediateFailedFuture(Throwable)} with {@link Exception}
213      * containing message in {@link Exception#getMessage()}
214      * 
215      * @param bridgeDomainName bridge domain
216      * @param addrEpWithLoc {@link AddressEndpointWithLocation} containing
217      *        {@link ExternalLocationCase} where
218      *        {@link ExternalLocationCase#getExternalNodeMountPoint()} MUST NOT be {@code null}
219      *        and {@link ExternalLocationCase#getExternalNodeConnector()} MUST NOT be {@code null}
220      * @return {@link ListenableFuture}
221      */
222     public synchronized @Nonnull ListenableFuture<Void> addBridgeDomainToInterface(@Nonnull String bridgeDomainName,
223             @Nonnull AddressEndpointWithLocation addrEpWithLoc) {
224         ExternalLocationCase epLoc = resolveAndValidateLocation(addrEpWithLoc);
225         InstanceIdentifier<?> vppNodeIid = epLoc.getExternalNodeMountPoint();
226         String interfacePath = epLoc.getExternalNodeConnector();
227
228         Optional<InstanceIdentifier<Interface>> optInterfaceIid =
229                 VppPathMapper.interfaceToInstanceIdentifier(interfacePath);
230         if (!optInterfaceIid.isPresent()) {
231             return Futures.immediateFailedFuture(
232                     new Exception("Cannot resolve interface instance-identifier for interface path" + interfacePath));
233         }
234         InstanceIdentifier<Interface> interfaceIid = optInterfaceIid.get();
235
236         Optional<DataBroker> potentialVppDataProvider = mountDataProvider.getDataBrokerForMountPoint(vppNodeIid);
237         if (!potentialVppDataProvider.isPresent()) {
238             return Futures.immediateFailedFuture(new Exception("Cannot get data broker for mount point " + vppNodeIid));
239         }
240
241         ReadWriteTransaction rwTx = potentialVppDataProvider.get().newReadWriteTransaction();
242         CheckedFuture<Optional<Interface>, ReadFailedException> futureIface =
243                 rwTx.read(LogicalDatastoreType.CONFIGURATION, interfaceIid);
244         return Futures.transform(futureIface, new AsyncFunction<Optional<Interface>, Void>() {
245
246             @Override
247             public ListenableFuture<Void> apply(Optional<Interface> optIface) throws Exception {
248                 if (!optIface.isPresent()) {
249                     return Futures.immediateFailedFuture(new Exception("Iterface "
250                             + interfaceIid.firstKeyOf(Interface.class) + " does not exist on node " + vppNodeIid));
251                 }
252
253                 String existingBridgeDomain = resolveBridgeDomain(optIface.get());
254                 if (bridgeDomainName.equals(existingBridgeDomain)) {
255                     LOG.debug("Bridge domain {} already exists on interface {}", bridgeDomainName, interfacePath);
256                     String bridgeDomainPath = VppPathMapper.bridgeDomainToRestPath(bridgeDomainName);
257                     if (!bridgeDomainPath.equals(epLoc.getExternalNode())) {
258                         vppEndpointLocationProvider.replaceLocationForEndpoint(new ExternalLocationCaseBuilder()
259                             .setExternalNode(bridgeDomainPath)
260                             .setExternalNodeMountPoint(vppNodeIid)
261                             .setExternalNodeConnector(interfacePath)
262                             .build(), addrEpWithLoc.getKey());
263                     }
264                     return Futures.immediateFuture(null);
265                 }
266
267                 InstanceIdentifier<L2> l2Iid =
268                         interfaceIid.builder().augmentation(VppInterfaceAugmentation.class).child(L2.class).build();
269                 L2 l2 = new L2Builder()
270                     .setInterconnection(new BridgeBasedBuilder().setBridgeDomain(bridgeDomainName).build()).build();
271                 rwTx.merge(LogicalDatastoreType.CONFIGURATION, l2Iid, l2);
272                 LOG.debug("Adding bridge domain {} to interface {}", bridgeDomainName, interfacePath);
273                 return Futures.transform(rwTx.submit(), new Function<Void, Void>() {
274
275                     @Override
276                     public Void apply(Void input) {
277                         String bridgeDomainPath = VppPathMapper.bridgeDomainToRestPath(bridgeDomainName);
278                         vppEndpointLocationProvider.replaceLocationForEndpoint(new ExternalLocationCaseBuilder()
279                                 .setExternalNode(bridgeDomainPath)
280                                 .setExternalNodeMountPoint(vppNodeIid)
281                                 .setExternalNodeConnector(interfacePath)
282                                 .build(), addrEpWithLoc.getKey());
283                         return null;
284                     }
285                 }, netconfWorker);
286             }
287         }, netconfWorker);
288     }
289
290     /**
291      * <p>
292      * Removes bridge domain (if exist) from an interface (if exist).<br>
293      * {@link VppEndpointLocationProvider#VPP_ENDPOINT_LOCATION_PROVIDER} will update endpoint
294      * location.
295      * <p>
296      * If the interface does not exist or other problems occur {@link ListenableFuture} will fail
297      * as {@link Futures#immediateFailedFuture(Throwable)} with {@link Exception}
298      * containing message in {@link Exception#getMessage()}
299      * 
300      * @param addrEpWithLoc {@link AddressEndpointWithLocation} containing
301      *        {@link ExternalLocationCase} where
302      *        {@link ExternalLocationCase#getExternalNodeMountPoint()} MUST NOT be {@code null}
303      *        and {@link ExternalLocationCase#getExternalNodeConnector()} MUST NOT be {@code null}
304      * @return {@link ListenableFuture}
305      */
306     public synchronized @Nonnull ListenableFuture<Void> deleteBridgeDomainFromInterface(
307             @Nonnull AddressEndpointWithLocation addrEpWithLoc) {
308         ExternalLocationCase epLoc = resolveAndValidateLocation(addrEpWithLoc);
309         InstanceIdentifier<?> vppNodeIid = epLoc.getExternalNodeMountPoint();
310         String interfacePath = epLoc.getExternalNodeConnector();
311
312         Optional<InstanceIdentifier<Interface>> optInterfaceIid =
313                 VppPathMapper.interfaceToInstanceIdentifier(interfacePath);
314         if (!optInterfaceIid.isPresent()) {
315             return Futures.immediateFailedFuture(
316                     new Exception("Cannot resolve interface instance-identifier for interface path" + interfacePath));
317         }
318         InstanceIdentifier<Interface> interfaceIid = optInterfaceIid.get();
319
320         Optional<DataBroker> potentialVppDataProvider = mountDataProvider.getDataBrokerForMountPoint(vppNodeIid);
321         if (!potentialVppDataProvider.isPresent()) {
322             return Futures.immediateFailedFuture(new Exception("Cannot get data broker for mount point " + vppNodeIid));
323         }
324
325         ReadWriteTransaction rwTx = potentialVppDataProvider.get().newReadWriteTransaction();
326         CheckedFuture<Optional<Interface>, ReadFailedException> futureIface =
327                 rwTx.read(LogicalDatastoreType.CONFIGURATION, interfaceIid);
328         return Futures.transform(futureIface, new AsyncFunction<Optional<Interface>, Void>() {
329
330             @Override
331             public ListenableFuture<Void> apply(Optional<Interface> optIface) throws Exception {
332                 if (!optIface.isPresent()) {
333                     // interface does not exist so we consider job done
334                     return Futures.immediateFuture(null);
335                 }
336
337                 String existingBridgeDomain = resolveBridgeDomain(optIface.get());
338                 if (Strings.isNullOrEmpty(existingBridgeDomain)) {
339                     LOG.debug("Bridge domain does not exist therefore it is cosidered as"
340                             + "deleted for interface {}", interfacePath);
341                     // bridge domain does not exist on interface so we consider job done
342                     vppEndpointLocationProvider.replaceLocationForEndpoint(new ExternalLocationCaseBuilder()
343                             .setExternalNode(null)
344                             .setExternalNodeMountPoint(vppNodeIid)
345                             .setExternalNodeConnector(interfacePath)
346                             .build(), addrEpWithLoc.getKey());
347                     return Futures.immediateFuture(null);
348                 }
349
350                 InstanceIdentifier<L2> l2Iid =
351                         interfaceIid.builder().augmentation(VppInterfaceAugmentation.class).child(L2.class).build();
352                 rwTx.delete(LogicalDatastoreType.CONFIGURATION, l2Iid);
353                 LOG.debug("Deleting bridge domain from interface {}", interfacePath);
354                 return Futures.transform(rwTx.submit(), new Function<Void, Void>() {
355
356                     @Override
357                     public Void apply(Void input) {
358                         vppEndpointLocationProvider.replaceLocationForEndpoint(new ExternalLocationCaseBuilder()
359                                 .setExternalNode(null)
360                                 .setExternalNodeMountPoint(vppNodeIid)
361                                 .setExternalNodeConnector(interfacePath)
362                                 .build(), addrEpWithLoc.getKey());
363                         return null;
364                     }
365                 }, netconfWorker);
366             }
367         }, netconfWorker);
368     }
369
370     private static ExternalLocationCase resolveAndValidateLocation(AddressEndpointWithLocation addrEpWithLoc) {
371         LocationType locationType = addrEpWithLoc.getAbsoluteLocation().getLocationType();
372         if (!(locationType instanceof ExternalLocationCase)) {
373             throw new IllegalArgumentException("Endpoint does not have external location " + addrEpWithLoc);
374         }
375         ExternalLocationCase result = (ExternalLocationCase) locationType;
376         if (result.getExternalNodeMountPoint() == null || result.getExternalNodeConnector() == null) {
377             throw new IllegalArgumentException(
378                     "Endpoint does not have external-node-mount-point or external-node-connector " + addrEpWithLoc);
379         }
380         return result;
381     }
382
383     private static @Nullable String resolveBridgeDomain(@Nonnull Interface iface) {
384         VppInterfaceAugmentation vppInterfaceAugmentation = iface.getAugmentation(VppInterfaceAugmentation.class);
385         L2 existingL2 = vppInterfaceAugmentation.getL2();
386         if (existingL2 != null) {
387             Interconnection interconnection = existingL2.getInterconnection();
388             if (interconnection instanceof BridgeBased) {
389                 return ((BridgeBased) interconnection).getBridgeDomain();
390             }
391         }
392         return null;
393     }
394
395     private static boolean hasNodeAndInterface(VppEndpoint vppEp) {
396         if (vppEp.getVppNodePath() == null) {
397             LOG.trace("vpp-node is missing. {}", vppEp);
398             return false;
399         }
400         if (Strings.isNullOrEmpty(vppEp.getVppInterfaceName())) {
401             LOG.trace("vpp-interface-name is missing. {}", vppEp);
402             return false;
403         }
404         return true;
405     }
406
407     @Override
408     public void close() throws Exception {
409         vppEndpointLocationProvider.close();
410     }
411
412 }