Freeze upstream versions
[netvirt.git] / neutronvpn / impl / src / main / java / org / opendaylight / netvirt / neutronvpn / NeutronTrunkChangeListener.java
1 /*
2  * Copyright (c) 2017, 2018 Ericsson India Global Services Pvt Ltd. 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 package org.opendaylight.netvirt.neutronvpn;
9
10 import static org.opendaylight.mdsal.binding.util.Datastore.CONFIGURATION;
11
12 import com.google.common.base.Preconditions;
13 import com.google.common.util.concurrent.ListenableFuture;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Objects;
19 import javax.annotation.PreDestroy;
20 import javax.inject.Inject;
21 import javax.inject.Singleton;
22 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
23 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
24 import org.opendaylight.infrautils.utils.concurrent.Executors;
25 import org.opendaylight.infrautils.utils.concurrent.LoggingFutures;
26 import org.opendaylight.mdsal.binding.api.DataBroker;
27 import org.opendaylight.mdsal.binding.util.ManagedNewTransactionRunner;
28 import org.opendaylight.mdsal.binding.util.ManagedNewTransactionRunnerImpl;
29 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
30 import org.opendaylight.serviceutils.tools.listener.AbstractAsyncDataTreeChangeListener;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev170119.L2vlan;
32 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
33 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.InterfaceBuilder;
34 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.IfL2vlan;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.IfL2vlanBuilder;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.ParentRefs;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.ParentRefsBuilder;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.SplitHorizon;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.SplitHorizonBuilder;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.types.rev130827.VlanId;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.port.id.subport.data.PortIdToSubportBuilder;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.port.id.subport.data.PortIdToSubportKey;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.networks.rev150712.NetworkTypeVlan;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.trunks.rev170118.trunk.attributes.SubPorts;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.trunks.rev170118.trunk.attributes.SubPortsKey;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.trunks.rev170118.trunks.attributes.Trunks;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.trunks.rev170118.trunks.attributes.trunks.Trunk;
50 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 @Singleton
55 public class NeutronTrunkChangeListener extends AbstractAsyncDataTreeChangeListener<Trunk> {
56     private static final Logger LOG = LoggerFactory.getLogger(NeutronTrunkChangeListener.class);
57
58     private final DataBroker dataBroker;
59     private final ManagedNewTransactionRunner txRunner;
60     private final IInterfaceManager ifMgr;
61     private final JobCoordinator jobCoordinator;
62
63     @Inject
64     public NeutronTrunkChangeListener(final DataBroker dataBroker, final IInterfaceManager ifMgr,
65             final JobCoordinator jobCoordinator) {
66         super(dataBroker, LogicalDatastoreType.CONFIGURATION,
67                 InstanceIdentifier.create(Neutron.class).child(Trunks.class).child(Trunk.class),
68                 Executors.newSingleThreadExecutor("NeutronTrunkChangeListener", LOG));
69         this.dataBroker = dataBroker;
70         this.txRunner = new ManagedNewTransactionRunnerImpl(dataBroker);
71         this.ifMgr = ifMgr;
72         this.jobCoordinator = jobCoordinator;
73     }
74
75     public void init() {
76         LOG.info("{} init", getClass().getSimpleName());
77     }
78
79     @Override
80     @PreDestroy
81     public void close() {
82         super.close();
83         Executors.shutdownAndAwaitTermination(getExecutorService());
84     }
85
86     @Override
87     public void add(InstanceIdentifier<Trunk> identifier, Trunk input) {
88         Preconditions.checkNotNull(input.getPortId());
89         LOG.trace("Adding Trunk : key: {}, value={}", identifier, input);
90         Map<SubPortsKey, SubPorts> keySubPortsMap = input.getSubPorts();
91         if (keySubPortsMap != null) {
92             keySubPortsMap.values().forEach(subPort -> createSubPortInterface(input, subPort));
93         }
94     }
95
96     @Override
97     public void remove(InstanceIdentifier<Trunk> identifier, Trunk input) {
98         Preconditions.checkNotNull(input.getPortId());
99         LOG.trace("Removing Trunk : key: {}, value={}", identifier, input);
100         Map<SubPortsKey, SubPorts> keySubPortsMap = input.getSubPorts();
101         if (keySubPortsMap != null) {
102             keySubPortsMap.values().forEach(this::deleteSubPortInterface);
103         }
104     }
105
106     @Override
107     public void update(InstanceIdentifier<Trunk> identifier, Trunk original, Trunk update) {
108         if (Objects.equals(original, update)) {
109             return;
110         }
111         List<SubPorts> updatedSubPorts = new ArrayList<>(update.nonnullSubPorts().values());
112         if (updatedSubPorts == null) {
113             updatedSubPorts = Collections.emptyList();
114         }
115         List<SubPorts> originalSubPorts = new ArrayList<>(original.nonnullSubPorts().values());
116         if (originalSubPorts == null) {
117             originalSubPorts = Collections.emptyList();
118         }
119         List<SubPorts> added = new ArrayList<>(updatedSubPorts);
120         added.removeAll(originalSubPorts);
121         List<SubPorts> deleted = new ArrayList<>(originalSubPorts);
122         deleted.removeAll(updatedSubPorts);
123
124         LOG.trace("Updating Trunk : key: {}. subPortsAdded={}, subPortsDeleted={}", identifier, added, deleted);
125         deleted.forEach(this::deleteSubPortInterface);
126         added.forEach(subPort -> createSubPortInterface(update, subPort));
127     }
128
129     private void createSubPortInterface(Trunk trunk, SubPorts subPort) {
130         if (!NetworkTypeVlan.class.equals(subPort.getSegmentationType())) {
131             LOG.warn("SegmentationType other than VLAN not supported for Trunk:SubPorts");
132             return;
133         }
134         String portName = subPort.getPortId().getValue();
135         String parentName = trunk.getPortId().getValue();
136         InstanceIdentifier<Interface> interfaceIdentifier = NeutronvpnUtils.buildVlanInterfaceIdentifier(portName);
137
138         // Should we use parentName?
139         jobCoordinator.enqueueJob("PORT- " + portName, () -> {
140             /*
141             *  Build Port-to-Subport details first, irrespective of port being available or not.
142             */
143             PortIdToSubportBuilder portIdToSubportBuilder = new PortIdToSubportBuilder();
144             Uuid subPortUuid = subPort.getPortId();
145             portIdToSubportBuilder.withKey(new PortIdToSubportKey(subPortUuid)).setPortId(subPortUuid)
146                     .setTrunkPortId(trunk.getPortId()).setVlanId(subPort.getSegmentationId());
147             List<ListenableFuture<?>> futures = new ArrayList<>();
148             futures.add(txRunner.callWithNewWriteOnlyTransactionAndSubmit(
149                 CONFIGURATION, tx -> {
150                     tx.merge(NeutronvpnUtils.buildPortIdSubportMappingIdentifier(subPortUuid),
151                             portIdToSubportBuilder.build());
152                     LOG.trace("Creating PortIdSubportMapping for port{}", portName);
153                 }));
154
155             Interface iface = ifMgr.getInterfaceInfoFromConfigDataStore(portName);
156             if (iface == null) {
157                 /*
158                  * Trunk creation requires NeutronPort to be present, by this time interface
159                  * should've been created. In controller restart use case Interface would already be present.
160                  * Clustering consideration:
161                  *      This being same shard as NeutronPort, interface creation will be triggered on the same
162                  *      node as this one. Use of DSJC helps ensure the order.
163                  */
164                 LOG.warn("Interface not present for Trunk SubPort: {}", subPort);
165                 return futures;
166             }
167             InterfaceBuilder interfaceBuilder = new InterfaceBuilder();
168             IfL2vlan ifL2vlan = new IfL2vlanBuilder().setL2vlanMode(IfL2vlan.L2vlanMode.TrunkMember)
169                 .setVlanId(new VlanId(subPort.getSegmentationId().intValue())).build();
170             ParentRefs parentRefs = new ParentRefsBuilder().setParentInterface(parentName).build();
171             SplitHorizon splitHorizon = new SplitHorizonBuilder().setOverrideSplitHorizonProtection(true).build();
172             interfaceBuilder.setName(portName).setType(L2vlan.class).addAugmentation(ifL2vlan)
173                 .addAugmentation(parentRefs).addAugmentation(splitHorizon);
174             Interface newIface = interfaceBuilder.build();
175             /*
176              * Interface is already created for parent NeutronPort. We're updating parent refs
177              * and VLAN Information
178              */
179             ListenableFuture<?> future = txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION,
180                 txn -> txn.merge(interfaceIdentifier, newIface));
181             LoggingFutures.addErrorLogging(future, LOG,
182                     "createSubPortInterface: Failed for portName {}, parentName {}", portName, parentName);
183             futures.add(future);
184             return futures;
185         });
186     }
187
188     private void deleteSubPortInterface(SubPorts subPort) {
189         String portName = subPort.getPortId().getValue();
190         InstanceIdentifier<Interface> interfaceIdentifier =
191                         NeutronvpnUtils.buildVlanInterfaceIdentifier(subPort.getPortId().getValue());
192         jobCoordinator.enqueueJob("PORT- " + portName, () -> {
193             Interface iface = ifMgr.getInterfaceInfoFromConfigDataStore(portName);
194             List<ListenableFuture<?>> futures = new ArrayList<>();
195             if (iface == null) {
196                 LOG.warn("Interface not present for SubPort {}", subPort);
197                 return futures;
198             }
199             /*
200              * We'll reset interface back to way it was? Can IFM handle parentRef delete?
201              */
202             InterfaceBuilder interfaceBuilder = new InterfaceBuilder(iface);
203             // Reset augmentations
204             interfaceBuilder.removeAugmentation(IfL2vlan.class).removeAugmentation(ParentRefs.class)
205                 .removeAugmentation(SplitHorizon.class);
206             IfL2vlan ifL2vlan = new IfL2vlanBuilder().setL2vlanMode(IfL2vlan.L2vlanMode.Trunk).build();
207             interfaceBuilder.addAugmentation(ifL2vlan);
208             Interface newIface = interfaceBuilder.build();
209             /*
210              * There is no means to do an update to remove elements from a node.
211              * Our solution is to get existing iface, remove parentRef and VlanId,
212              * and do a put to replace existing entry. This works out better as put
213              * has better performance than merge.
214              * Only drawback is any in-flight changes might be lost, but that is a corner case
215              * and this being subport delete path, don't expect any significant changes to
216              * corresponding Neutron Port. Deletion of NeutronPort should follow soon enough.
217              */
218             futures.add(txRunner.callWithNewWriteOnlyTransactionAndSubmit(
219                 CONFIGURATION, tx -> {
220                     tx.put(interfaceIdentifier, newIface);
221                     LOG.trace("Resetting trunk member interface {}", newIface);
222                 }));
223             futures.add(txRunner.callWithNewWriteOnlyTransactionAndSubmit(
224                 CONFIGURATION, tx -> {
225                     tx.delete(NeutronvpnUtils.buildPortIdSubportMappingIdentifier(subPort.getPortId()));
226                     LOG.trace("Deleting PortIdSubportMapping for portName {}", portName);
227                 }));
228             return futures;
229         });
230
231     }
232 }