Bug 8228 - metadata service fix made cleaner
[groupbasedpolicy.git] / renderers / vpp / src / main / java / org / opendaylight / groupbasedpolicy / renderer / vpp / iface / VppEndpointLocationProvider.java
index 93f770e8ac97eeefda6546e81fee9c354e88465d..3a32c8978792a8a6c1d9cbe020736e8dcdad94ce 100644 (file)
@@ -10,23 +10,41 @@ package org.opendaylight.groupbasedpolicy.renderer.vpp.iface;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
 import javax.annotation.Nonnull;
-import com.google.common.base.Function;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
+
 import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.groupbasedpolicy.renderer.vpp.manager.VppNodeManager;
 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.CloseOnFailTransactionChain;
 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.KeyFactory;
 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.VppIidFactory;
+import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
+import org.opendaylight.groupbasedpolicy.util.DataTreeChangeHandler;
+import org.opendaylight.groupbasedpolicy.util.EndpointUtils;
 import org.opendaylight.groupbasedpolicy.util.IidFactory;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.endpoints.address.endpoints.AddressEndpointKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.Endpoints;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.endpoints.AddressEndpoints;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.endpoints.address.endpoints.AddressEndpoint;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.absolute.location.AbsoluteLocation;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.absolute.location.AbsoluteLocationBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.absolute.location.absolute.location.location.type.ExternalLocationCase;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.absolute.location.absolute.location.location.type.ExternalLocationCaseBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.relative.location.RelativeLocations;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.relative.location.RelativeLocationsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.relative.location.relative.locations.ExternalLocationBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.parent.child.endpoints.parent.endpoint.choice.parent.endpoint._case.ParentEndpoint;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.parent.child.endpoints.parent.endpoint.choice.parent.endpoint._case.ParentEndpointKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint_location_provider.rev160419.ProviderName;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint_location_provider.rev160419.location.providers.LocationProvider;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint_location_provider.rev160419.location.providers.LocationProviderBuilder;
@@ -35,20 +53,32 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint_l
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint_location_provider.rev160419.location.providers.location.provider.ProviderAddressEndpointLocationKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.endpoints.AddressEndpointWithLocationKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpoint;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpointBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpointKey;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class VppEndpointLocationProvider implements AutoCloseable {
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+public class VppEndpointLocationProvider extends DataTreeChangeHandler<AddressEndpoint> {
 
     private static final Logger LOG = LoggerFactory.getLogger(VppEndpointLocationProvider.class);
     public static final ProviderName VPP_ENDPOINT_LOCATION_PROVIDER =
             new ProviderName("VPP endpoint location provider");
     public static final long PROVIDER_PRIORITY = 10L;
     private final BindingTransactionChain txChain;
+    private final Map<VppEndpointKey, VppEndpoint> vppEndpoints = new HashMap<>();
+    private final Map<VppEndpointKey, DataObjectModification<AddressEndpoint>> cachedVppEndpoints = new HashMap<>();
 
     public VppEndpointLocationProvider(DataBroker dataProvider) {
+        super(dataProvider);
         LocationProvider locationProvider = new LocationProviderBuilder().setProvider(VPP_ENDPOINT_LOCATION_PROVIDER)
             .setPriority(PROVIDER_PRIORITY)
             .build();
@@ -70,54 +100,171 @@ public class VppEndpointLocationProvider implements AutoCloseable {
                 LOG.error("{} was NOT created", VPP_ENDPOINT_LOCATION_PROVIDER.getValue());
             }
         });
+        registerDataTreeChangeListener(new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
+                InstanceIdentifier.builder(Endpoints.class).child(AddressEndpoints.class).child(AddressEndpoint.class).build()));
     }
 
-    ListenableFuture<Void> createLocationForVppEndpoint(final VppEndpoint vppEndpoint) {
-        final ProviderAddressEndpointLocation providerAddressEndpointLocation =
-                createProviderAddressEndpointLocation(vppEndpoint);
-        final WriteTransaction wTx = txChain.newWriteOnlyTransaction();
-        wTx.put(LogicalDatastoreType.CONFIGURATION, IidFactory.providerAddressEndpointLocationIid(
-                VPP_ENDPOINT_LOCATION_PROVIDER, providerAddressEndpointLocation.getKey()),
-                providerAddressEndpointLocation);
-        LOG.debug("Creating location for {}", providerAddressEndpointLocation.getKey());
-        return Futures.transform(wTx.submit(), (Function<Void, Void>) input -> {
-            LOG.debug("{} provided location: {}", VPP_ENDPOINT_LOCATION_PROVIDER.getValue(),
-                    providerAddressEndpointLocation);
-            return null;
-        });
+    @Override
+    protected void onWrite(DataObjectModification<AddressEndpoint> rootNode,
+            InstanceIdentifier<AddressEndpoint> rootIdentifier) {
+        LOG.debug("onWrite triggered by {}", rootNode.getDataAfter());
+        try {
+            if (EndpointUtils.isExternalEndpoint(dataProvider, rootNode.getDataAfter())) {
+                writeLocation(createRelativeAddressEndpointLocation(rootNode.getDataAfter().getKey(),
+                        VppNodeManager.resolvePublicInterfaces(dataProvider))).get();
+            } else {
+                createAbsoluteAddressEndpointLocation(null, rootNode).get();
+            }
+        } catch (InterruptedException | ExecutionException e) {
+            LOG.error("Failed to write location for endpoint {}. {}", rootNode.getDataAfter().getKey(), e.getMessage());
+        }
+    }
+
+    @Override
+    protected void onDelete(DataObjectModification<AddressEndpoint> rootNode,
+            InstanceIdentifier<AddressEndpoint> rootIdentifier) {
+        LOG.debug("onDelete triggered by {}", rootNode.getDataBefore());
+        try {
+            if (EndpointUtils.isExternalEndpoint(dataProvider, rootNode.getDataBefore())) {
+                deleteLocation(createProviderAddressEndpointLocationKey(rootNode.getDataBefore().getKey())).get();
+            } else {
+                createAbsoluteAddressEndpointLocation(null, rootNode).get();
+            }
+        } catch (InterruptedException | ExecutionException e) {
+            LOG.error("Failed to delete location for endpoint {}. {}", rootNode.getDataBefore().getKey(),
+                    e.getMessage());
+        }
+    }
+
+    @Override
+    protected void onSubtreeModified(DataObjectModification<AddressEndpoint> rootNode,
+            InstanceIdentifier<AddressEndpoint> rootIdentifier) {
+        LOG.debug("onSubtreeModified triggered by change: before={} after={}", rootNode.getDataBefore(),
+                rootNode.getDataAfter());
+        if (rootNode.getDataBefore() != null) {
+            onDelete(rootNode, rootIdentifier);
+        }
+        if (rootNode.getDataAfter() != null) {
+            onWrite(rootNode, rootIdentifier);
+        }
+    }
+
+    public ListenableFuture<Void> createLocationForVppEndpoint(VppEndpoint vppEndpoint) {
+        return createAbsoluteAddressEndpointLocation(vppEndpoint, null);
+    }
+
+    public ListenableFuture<Void> deleteLocationForVppEndpoint(VppEndpoint vppEndpoint) {
+        // removing VPP EP from cache out of since block, it's not needed for the other thread.
+        vppEndpoints.remove(vppEndpoint.getKey());
+        return deleteLocation(createProviderAddressEndpointLocationKey(vppEndpoint));
+    }
+
+    /**
+     * There are two inputs from which we need to resolve location - {@link AddressEndpoint} and {@link VppEndpoint}
+     * These data are delivered by different threads which meet here.
+     */
+    @VisibleForTesting
+    synchronized ListenableFuture<Void> createAbsoluteAddressEndpointLocation(VppEndpoint vppEndpoint,
+            DataObjectModification<AddressEndpoint> rootNode) {
+        if (vppEndpoint != null) {
+            vppEndpoints.put(vppEndpoint.getKey(), vppEndpoint);
+            if (cachedVppEndpoints.get(vppEndpoint.getKey()) != null) {
+                return processAddrEp(cachedVppEndpoints.get(vppEndpoint.getKey()));
+            }
+        } else if (rootNode != null) {
+            return processAddrEp(rootNode);
+        }
+        return Futures.immediateFuture(null);
+    }
+
+    private ListenableFuture<Void> processAddrEp(DataObjectModification<AddressEndpoint> rootNode) {
+        if (rootNode != null) {
+            AddressEndpointChange aec = new AddressEndpointChange(rootNode, dataProvider);
+            switch (rootNode.getModificationType()) {
+                case WRITE:
+                case SUBTREE_MODIFIED: {
+                    VppEndpoint vpp = vppEndpoints.get(vppEndpointKeyFrom(rootNode.getDataAfter().getKey()));
+                    if (vpp == null) {
+                        VppEndpointKey key = vppEndpointKeyFrom(rootNode.getDataAfter().getKey());
+                        cachedVppEndpoints.put(key, rootNode);
+                        return Futures.immediateFuture(null);
+                    }
+                    if (aec.hasMoreParents()) {
+                        return aec.syncMultiparents();
+                    }
+                        return aec.write();
+                }
+                case DELETE: {
+                    if (aec.hasMoreParents()) {
+                        return aec.syncMultiparents();
+                    } else {
+                        return aec.delete();
+                    }
+                }
+            }
+        }
+        return Futures.immediateFuture(null);
     }
 
-    public static ProviderAddressEndpointLocation createProviderAddressEndpointLocation(VppEndpoint vppEndpoint) {
+    private ProviderAddressEndpointLocation createAbsoluteLocationFromVppEndpoint(VppEndpoint vppEndpoint) {
         InstanceIdentifier<Node> vppNodeIid = VppIidFactory.getNetconfNodeIid(vppEndpoint.getVppNodeId());
         String restIfacePath = VppPathMapper.interfaceToRestPath(vppEndpoint.getVppInterfaceName());
-        AbsoluteLocation absoluteLocation = new AbsoluteLocationBuilder()
-            .setLocationType(new ExternalLocationCaseBuilder().setExternalNodeMountPoint(vppNodeIid)
-                .setExternalNodeConnector(restIfacePath)
-                .build())
-            .build();
+        AbsoluteLocation absoluteLocation =
+                new AbsoluteLocationBuilder().setLocationType(new ExternalLocationCaseBuilder()
+                    .setExternalNodeMountPoint(vppNodeIid).setExternalNodeConnector(restIfacePath).build()).build();
         return new ProviderAddressEndpointLocationBuilder()
             .setKey(createProviderAddressEndpointLocationKey(vppEndpoint))
             .setAbsoluteLocation(absoluteLocation)
             .build();
     }
 
-    ListenableFuture<Void> deleteLocationForVppEndpoint(final VppEndpoint vppEndpoint) {
-        final ProviderAddressEndpointLocationKey provAddrEpLocKey =
-                createProviderAddressEndpointLocationKey(vppEndpoint);
-        final WriteTransaction wTx = txChain.newWriteOnlyTransaction();
-        wTx.delete(LogicalDatastoreType.CONFIGURATION,
-                IidFactory.providerAddressEndpointLocationIid(VPP_ENDPOINT_LOCATION_PROVIDER, provAddrEpLocKey));
-        LOG.debug("Deleting location for {}", provAddrEpLocKey);
-        return Futures.transform(wTx.submit(), (Function<Void, Void>) input -> {
-            LOG.debug("{} removed location: {}", VPP_ENDPOINT_LOCATION_PROVIDER.getValue(), provAddrEpLocKey);
-            return null;
+    public ProviderAddressEndpointLocation createRelativeAddressEndpointLocation(@Nonnull AddressEndpointKey addrEp,
+            @Nonnull Map<NodeId, String> publicIntfNamesByNodes) {
+        RelativeLocations relLocations =
+                new RelativeLocationsBuilder()
+                    .setExternalLocation(publicIntfNamesByNodes.keySet()
+                        .stream()
+                        .filter(nodeId -> publicIntfNamesByNodes.get(nodeId) != null)
+                        .map(nodeId -> new ExternalLocationBuilder()
+                            .setExternalNodeMountPoint(VppIidFactory.getNetconfNodeIid(nodeId))
+                            .setExternalNodeConnector(
+                                    VppPathMapper.interfaceToRestPath(publicIntfNamesByNodes.get(nodeId)))
+                            .build())
+                        .collect(Collectors.toList()))
+                    .build();
+        return new ProviderAddressEndpointLocationBuilder().setKey(createProviderAddressEndpointLocationKey(addrEp))
+            .setRelativeLocations(relLocations)
+            .build();
+    }
+
+    public ListenableFuture<Void> writeLocation(ProviderAddressEndpointLocation location) {
+        WriteTransaction wTx = txChain.newWriteOnlyTransaction();
+        wTx.put(LogicalDatastoreType.CONFIGURATION,
+                IidFactory.providerAddressEndpointLocationIid(VPP_ENDPOINT_LOCATION_PROVIDER, location.getKey()),
+                location, true);
+        return Futures.transform(wTx.submit(), new Function<Void, Void>() {
+
+            @Override
+            public Void apply(Void input) {
+                LOG.debug("{} provided location: {}", VPP_ENDPOINT_LOCATION_PROVIDER.getValue(), location);
+                return null;
+            }
         });
     }
 
-    private static ProviderAddressEndpointLocationKey createProviderAddressEndpointLocationKey(
-            VppEndpoint vppEndpoint) {
-        return new ProviderAddressEndpointLocationKey(vppEndpoint.getAddress(), vppEndpoint.getAddressType(),
-                vppEndpoint.getContextId(), vppEndpoint.getContextType());
+    public ListenableFuture<Void> deleteLocation(ProviderAddressEndpointLocationKey key) {
+        ReadWriteTransaction rwTx = txChain.newReadWriteTransaction();
+        LOG.debug("Deleting location for {}", key);
+        DataStoreHelper.removeIfExists(LogicalDatastoreType.CONFIGURATION,
+                IidFactory.providerAddressEndpointLocationIid(VPP_ENDPOINT_LOCATION_PROVIDER, key), rwTx);
+        return Futures.transform(rwTx.submit(), new Function<Void, Void>() {
+
+            @Override
+            public Void apply(Void input) {
+                LOG.debug("{} removed location: {}", VPP_ENDPOINT_LOCATION_PROVIDER.getValue(), key);
+                return null;
+            }
+        });
     }
 
     public ListenableFuture<Void> replaceLocationForEndpoint(@Nonnull ExternalLocationCase location, @Nonnull AddressEndpointWithLocationKey addrEpWithLocKey) {
@@ -144,10 +291,95 @@ public class VppEndpointLocationProvider implements AutoCloseable {
         });
     }
 
+    static ProviderAddressEndpointLocationKey createProviderAddressEndpointLocationKey(VppEndpoint vpp) {
+        return new ProviderAddressEndpointLocationKey(vpp.getAddress(), vpp.getAddressType(), vpp.getContextId(),
+                vpp.getContextType());
+    }
+
+    static ProviderAddressEndpointLocationKey createProviderAddressEndpointLocationKey(AddressEndpointKey key) {
+        return new ProviderAddressEndpointLocationKey(key.getAddress(), key.getAddressType(), key.getContextId(),
+                key.getContextType());
+    }
+
+    private static ProviderAddressEndpointLocationKey createProviderAddressEndpointLocationKey(ParentEndpointKey key) {
+        return new ProviderAddressEndpointLocationKey(key.getAddress(), key.getAddressType(), key.getContextId(),
+                key.getContextType());
+    }
+
+    private VppEndpointKey vppEndpointKeyFrom(AddressEndpointKey key) {
+        return new VppEndpointKey(key.getAddress(), key.getAddressType(), key.getContextId(), key.getContextType());
+    }
+
+    private VppEndpointKey vppEndpointKeyFrom(ParentEndpointKey key) {
+        return new VppEndpointKey(key.getAddress(), key.getAddressType(), key.getContextId(), key.getContextType());
+    }
+
     @Override
-    public void close() throws Exception {
+    public void close() {
+        super.closeRegisteredListener();
         WriteTransaction wTx = txChain.newWriteOnlyTransaction();
         wTx.delete(LogicalDatastoreType.CONFIGURATION, IidFactory.locationProviderIid(VPP_ENDPOINT_LOCATION_PROVIDER));
         wTx.submit();
     }
+
+    private class AddressEndpointChange {
+
+        private final AddressEndpoint before;
+        private final AddressEndpoint after;
+        private final DataBroker dataBroker;
+
+        public AddressEndpointChange(DataObjectModification<AddressEndpoint> addrEp, @Nonnull DataBroker dataBroker) {
+            this.before = addrEp.getDataBefore();
+            this.after = addrEp.getDataAfter();
+            this.dataBroker = dataBroker;
+        }
+
+        boolean hasMoreParents() {
+            return (before != null && EndpointUtils.getParentEndpoints(before.getParentEndpointChoice()).size() > 1)
+                    || (after != null && EndpointUtils.getParentEndpoints(after.getParentEndpointChoice()).size() > 1);
+        }
+
+        ListenableFuture<Void> syncMultiparents() {
+            ReadWriteTransaction rwTx = dataBroker.newReadWriteTransaction();
+            if (before != null) {
+                for (ParentEndpoint pe : EndpointUtils.getParentEndpoints(before.getParentEndpointChoice())) {
+                    InstanceIdentifier<ProviderAddressEndpointLocation> iid =
+                            IidFactory.providerAddressEndpointLocationIid(VPP_ENDPOINT_LOCATION_PROVIDER,
+                                    createProviderAddressEndpointLocationKey(pe.getKey()));
+                    DataStoreHelper.removeIfExists(LogicalDatastoreType.CONFIGURATION, iid, rwTx);
+                }
+            }
+            if (after != null) {
+                for (ParentEndpoint pe : EndpointUtils.getParentEndpoints(after.getParentEndpointChoice())) {
+                    VppEndpoint vppEndpoint = vppEndpoints.get(vppEndpointKeyFrom(after.getKey()));
+                    InstanceIdentifier<ProviderAddressEndpointLocation> iid =
+                            IidFactory.providerAddressEndpointLocationIid(VPP_ENDPOINT_LOCATION_PROVIDER,
+                                    createProviderAddressEndpointLocationKey(pe.getKey()));
+                    ProviderAddressEndpointLocation location = createAbsoluteLocationFromVppEndpoint(
+                            new VppEndpointBuilder(vppEndpoint).setKey(vppEndpointKeyFrom(pe.getKey())).build());
+                    rwTx.put(LogicalDatastoreType.CONFIGURATION, iid, location, true);
+                }
+            }
+            return rwTx.submit();
+        }
+
+        ListenableFuture<Void> write() {
+            VppEndpoint vpp = vppEndpoints.get(vppEndpointKeyFrom(after.getKey()));
+            WriteTransaction wTx = dataBroker.newWriteOnlyTransaction();
+            ProviderAddressEndpointLocation location =
+                    createAbsoluteLocationFromVppEndpoint(vpp);
+            InstanceIdentifier<ProviderAddressEndpointLocation> iid = IidFactory.providerAddressEndpointLocationIid(
+                    VPP_ENDPOINT_LOCATION_PROVIDER, createProviderAddressEndpointLocationKey(vpp));
+            wTx.put(LogicalDatastoreType.CONFIGURATION, iid, location, true);
+            return wTx.submit();
+        }
+
+        ListenableFuture<Void> delete() {
+            ReadWriteTransaction rwTx = dataBroker.newReadWriteTransaction();
+            InstanceIdentifier<ProviderAddressEndpointLocation> iid = IidFactory.providerAddressEndpointLocationIid(
+                    VPP_ENDPOINT_LOCATION_PROVIDER, createProviderAddressEndpointLocationKey(before.getKey()));
+            DataStoreHelper.removeIfExists(LogicalDatastoreType.CONFIGURATION, iid, rwTx);
+            return rwTx.submit();
+        }
+    }
 }