027cf0c55c2eb22fa6a0625b243f11e13f1be95d
[netvirt.git] / dhcpservice / impl / src / main / java / org / opendaylight / netvirt / dhcpservice / DhcpNeutronPortListener.java
1 /*
2  * Copyright (c) 2016 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.dhcpservice;
9
10 import static org.opendaylight.genius.infra.Datastore.CONFIGURATION;
11 import static org.opendaylight.genius.infra.Datastore.OPERATIONAL;
12
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.Locale;
17 import java.util.function.Consumer;
18 import javax.annotation.PreDestroy;
19 import javax.inject.Inject;
20 import javax.inject.Named;
21 import javax.inject.Singleton;
22 import org.opendaylight.genius.infra.ManagedNewTransactionRunner;
23 import org.opendaylight.genius.infra.ManagedNewTransactionRunnerImpl;
24 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
25 import org.opendaylight.genius.mdsalutil.NwConstants;
26 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
27 import org.opendaylight.infrautils.utils.concurrent.Executors;
28 import org.opendaylight.mdsal.binding.api.DataBroker;
29 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
30 import org.opendaylight.netvirt.dhcpservice.api.DhcpMConstants;
31 import org.opendaylight.netvirt.elan.arp.responder.ArpResponderInput;
32 import org.opendaylight.netvirt.elan.arp.responder.ArpResponderInput.ArpReponderInputBuilder;
33 import org.opendaylight.netvirt.elan.arp.responder.ArpResponderUtil;
34 import org.opendaylight.netvirt.elanmanager.api.ElanHelper;
35 import org.opendaylight.netvirt.elanmanager.api.IElanService;
36 import org.opendaylight.netvirt.neutronvpn.api.utils.NeutronConstants;
37 import org.opendaylight.serviceutils.tools.listener.AbstractClusteredAsyncDataTreeChangeListener;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.ItmRpcService;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.binding.rev150712.PortBindingExtension;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.port.attributes.FixedIps;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.Ports;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.Port;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.Subnet;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.dhcpservice.config.rev150710.DhcpserviceConfig;
47 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
48 import org.opendaylight.yangtools.yang.common.Uint64;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 @Singleton
53 public class DhcpNeutronPortListener extends AbstractClusteredAsyncDataTreeChangeListener<Port> {
54
55     private static final Logger LOG = LoggerFactory.getLogger(DhcpNeutronPortListener.class);
56     private final DhcpExternalTunnelManager dhcpExternalTunnelManager;
57     private final IElanService elanService;
58     private final DataBroker broker;
59     private final ManagedNewTransactionRunner txRunner;
60     private final DhcpserviceConfig config;
61     private final IInterfaceManager interfaceManager;
62     private final JobCoordinator jobCoordinator;
63     private final DhcpManager dhcpManager;
64     private final ItmRpcService itmRpcService;
65
66     @Inject
67     public DhcpNeutronPortListener(DataBroker db, DhcpExternalTunnelManager dhcpExternalTunnelManager,
68             @Named("elanService") IElanService ielanService, IInterfaceManager interfaceManager,
69             DhcpserviceConfig config, final JobCoordinator jobCoordinator, DhcpManager dhcpManager,
70             ItmRpcService itmRpcService) {
71         super(db, LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Neutron.class).child(Ports.class)
72                 .child(Port.class),
73                 Executors.newListeningSingleThreadExecutor("DhcpNeutronPortListener", LOG));
74         this.dhcpExternalTunnelManager = dhcpExternalTunnelManager;
75         this.elanService = ielanService;
76         this.interfaceManager = interfaceManager;
77         this.broker = db;
78         this.txRunner = new ManagedNewTransactionRunnerImpl(db);
79         this.config = config;
80         this.jobCoordinator = jobCoordinator;
81         this.dhcpManager = dhcpManager;
82         this.itmRpcService = itmRpcService;
83         init();
84     }
85
86     public void init() {
87         if (config.isControllerDhcpEnabled()) {
88             LOG.info("{} init", getClass().getSimpleName());
89         }
90     }
91
92     @Override
93     @PreDestroy
94     public void close() {
95         super.close();
96         Executors.shutdownAndAwaitTermination(getExecutorService());
97         LOG.debug("DhcpNeutronPortListener Listener Closed");
98     }
99
100     @Override
101     public void remove(InstanceIdentifier<Port> identifier, Port del) {
102         if (!config.isControllerDhcpEnabled()) {
103             return;
104         }
105         LOG.trace("Port removed: {}", del);
106         if (NeutronConstants.IS_ODL_DHCP_PORT.test(del)) {
107             jobCoordinator.enqueueJob(getJobKey(del),
108                 () -> Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, tx -> {
109                     java.util.Optional<String> ip4Address = DhcpServiceUtils.getIpV4Address(del);
110                     if (ip4Address.isPresent()) {
111                         dhcpExternalTunnelManager.addOrRemoveDhcpArpFlowforElan(del.getNetworkId().getValue(),
112                                 false, ip4Address.get(), del.getMacAddress().getValue());
113                     }
114                     DhcpServiceUtils.removeSubnetDhcpPortData(del, tx::delete);
115                     processArpResponderForElanDpns(del, arpInput -> {
116                         LOG.trace("Removing ARPResponder Flows  for dhcp port {} with ipaddress {} with mac {} "
117                                         + " on dpn {}. ",arpInput.getInterfaceName(), arpInput.getSpa(),
118                                         arpInput.getSha(), arpInput.getDpId());
119                         elanService.removeArpResponderFlow(arpInput);
120                     });
121                 })));
122         }
123         if (isVnicTypeDirectOrMacVtap(del)) {
124             removePort(del);
125         }
126     }
127
128     private String getJobKey(Port port) {
129         return "PORT- " + port.getUuid().getValue();
130     }
131
132     @Override
133     public void update(InstanceIdentifier<Port> identifier, Port original, Port update) {
134         if (!config.isControllerDhcpEnabled()) {
135             return;
136         }
137         LOG.trace("Port changed to {}", update);
138         //With Ipv6 changes we can get ipv4 subnets later. The below check is to support such scenario.
139         if (original.nonnullFixedIps().size() < update.nonnullFixedIps().size()) {
140             final String interfaceName = update.getUuid().getValue();
141             List<FixedIps> updatedFixedIps = new ArrayList<>(update.nonnullFixedIps().values());
142             // Need to check only the newly added fixed ip.
143             updatedFixedIps.removeAll(original.nonnullFixedIps().values());
144             Subnet subnet = dhcpManager.getNeutronSubnet(updatedFixedIps);
145             if (null == subnet || !subnet.isEnableDhcp()) {
146                 LOG.trace("Subnet is null/not ipv4 or not enabled {}", subnet);
147                 return;
148             }
149             // Binding the DHCP service for an existing port because of subnet change.
150             jobCoordinator.enqueueJob(DhcpServiceUtils.getJobKey(interfaceName),
151                 () -> Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, tx -> {
152                     LOG.debug("Binding DHCP service for interface {}", interfaceName);
153                     DhcpServiceUtils.bindDhcpService(interfaceName, NwConstants.DHCP_TABLE, tx);
154                 })), DhcpMConstants.RETRY_COUNT);
155             jobCoordinator.enqueueJob(DhcpServiceUtils.getJobKey(interfaceName), () -> {
156                 Uint64 dpnId = interfaceManager.getDpnForInterface(interfaceName);
157                 if (dpnId == null || dpnId.equals(DhcpMConstants.INVALID_DPID)) {
158                     LOG.trace("Unable to install the DHCP flow since dpn is not available");
159                     return Collections.emptyList();
160                 }
161                 String vmMacAddress = txRunner.applyWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
162                     tx -> DhcpServiceUtils.getAndUpdateVmMacAddress(tx, interfaceName, dhcpManager)).get();
163                 return Collections.singletonList(txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION,
164                     tx -> dhcpManager.installDhcpEntries(dpnId, vmMacAddress, tx)));
165             }, DhcpMConstants.RETRY_COUNT);
166         }
167         if (!isVnicTypeDirectOrMacVtap(update)) {
168             LOG.trace("Port updated is normal {}", update.getUuid());
169             if (isVnicTypeDirectOrMacVtap(original)) {
170                 LOG.trace("Original Port was direct/macvtap {} so removing flows and cache entry if any",
171                         update.getUuid());
172                 removePort(original);
173             }
174             return;
175         }
176         if (!isVnicTypeDirectOrMacVtap(original)) {
177             LOG.trace("Original port was normal and updated is direct. Calling addPort()");
178             addPort(update);
179             return;
180         }
181         String macOriginal = getMacAddress(original);
182         String macUpdated = getMacAddress(update);
183         String segmentationIdOriginal = DhcpServiceUtils.getSegmentationId(original.getNetworkId(), broker);
184         String segmentationIdUpdated = DhcpServiceUtils.getSegmentationId(update.getNetworkId(), broker);
185         if (macOriginal != null && !macOriginal.equalsIgnoreCase(macUpdated) && segmentationIdOriginal != null
186                 && !segmentationIdOriginal.equalsIgnoreCase(segmentationIdUpdated)) {
187             LOG.trace("Mac/segment id has changed");
188             dhcpExternalTunnelManager.removeVniMacToPortCache(Uint64.valueOf(segmentationIdOriginal), macOriginal);
189             dhcpExternalTunnelManager.updateVniMacToPortCache(Uint64.valueOf(segmentationIdUpdated),
190                     macUpdated, update);
191         }
192     }
193
194     @Override
195     public void add(InstanceIdentifier<Port> identifier, Port add) {
196         if (!config.isControllerDhcpEnabled()) {
197             return;
198         }
199         LOG.trace("Port added {}", add);
200         if (NeutronConstants.IS_ODL_DHCP_PORT.test(add)) {
201             jobCoordinator.enqueueJob(getJobKey(add),
202                 () -> Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, tx -> {
203                     DhcpServiceUtils.createSubnetDhcpPortData(add, tx::put);
204                     processArpResponderForElanDpns(add, arpInput -> {
205                         LOG.trace(
206                                 "Installing ARP RESPONDER Flows  for dhcp port {} ipaddress {} with mac {} on dpn {}",
207                                 arpInput.getInterfaceName(), arpInput.getSpa(), arpInput.getSha(),
208                                 arpInput.getDpId());
209                         ArpReponderInputBuilder builder = new ArpReponderInputBuilder(arpInput);
210                         builder.setInstructions(ArpResponderUtil.getInterfaceInstructions(interfaceManager,
211                                 arpInput.getInterfaceName(), arpInput.getSpa(), arpInput.getSha(), itmRpcService));
212                         elanService.addArpResponderFlow(builder.buildForInstallFlow());
213                     });
214                 })));
215             java.util.Optional<String> ip4Address = DhcpServiceUtils.getIpV4Address(add);
216             if (ip4Address.isPresent()) {
217                 dhcpExternalTunnelManager.addOrRemoveDhcpArpFlowforElan(add.getNetworkId().getValue(),
218                         true, ip4Address.get(), add.getMacAddress().getValue());
219             }
220         }
221         if (!isVnicTypeDirectOrMacVtap(add)) {
222             return;
223         }
224         addPort(add);
225     }
226
227     private void removePort(Port port) {
228         String macAddress = getMacAddress(port);
229         Uuid networkId = port.getNetworkId();
230         String segmentationId = DhcpServiceUtils.getSegmentationId(networkId, broker);
231         if (segmentationId == null) {
232             return;
233         }
234         List<Uint64> listOfDpns = DhcpServiceUtils.getListOfDpns(broker);
235         dhcpExternalTunnelManager.unInstallDhcpFlowsForVms(networkId.getValue(), listOfDpns, macAddress);
236         dhcpExternalTunnelManager.removeVniMacToPortCache(Uint64.valueOf(segmentationId), macAddress);
237     }
238
239     private void addPort(Port port) {
240         String macAddress = getMacAddress(port);
241         Uuid networkId = port.getNetworkId();
242         String segmentationId = DhcpServiceUtils.getSegmentationId(networkId, broker);
243         if (segmentationId == null) {
244             LOG.trace("segmentation id is null");
245             return;
246         }
247         dhcpExternalTunnelManager.updateVniMacToPortCache(Uint64.valueOf(segmentationId), macAddress, port);
248
249     }
250
251     private String getMacAddress(Port port) {
252         return port.getMacAddress().getValue();
253     }
254
255     private boolean isVnicTypeDirectOrMacVtap(Port port) {
256         PortBindingExtension portBinding = port.augmentation(PortBindingExtension.class);
257         if (portBinding == null || portBinding.getVnicType() == null) {
258             // By default, VNIC_TYPE is NORMAL
259             return false;
260         }
261         String vnicType = portBinding.getVnicType().trim().toLowerCase(Locale.getDefault());
262         return vnicType.equals("direct") || vnicType.equals("macvtap");
263     }
264
265     /**
266      * Handle(Add/Remove) ARP Responder for DHCP IP on all the DPNs when DHCP is
267      * enabled/disabled on subnet add or update or delete.
268      *
269      * @param port
270      *            DHCP port for which ARP Responder flow to be added when dhcp
271      *            flag is enabled on the subnet or DHCP port for which ARP
272      *            Responder flow to be removed when dhcp flag is disabled on the
273      *            Subnet
274      * @param arpResponderAction
275      *            ARP Responder Action to be performed i.e., add or remove flow
276      */
277     private void processArpResponderForElanDpns(Port port, Consumer<ArpResponderInput> arpResponderAction) {
278
279         java.util.Optional<String> ip4Address = DhcpServiceUtils.getIpV4Address(port);
280         if (!ip4Address.isPresent()) {
281             LOG.warn("There is no IPv4Address for port {}, not performing ARP responder add/remove flow operation",
282                     port.getName());
283             return;
284         }
285         ElanHelper.getDpnInterfacesInElanInstance(broker, port.getNetworkId().getValue()).stream()
286                 .map(ifName -> DhcpServiceUtils.getInterfaceInfo(interfaceManager, ifName)).forEach(interfaceInfo -> {
287                     ArpResponderInput arpResponderInput = new ArpResponderInput.ArpReponderInputBuilder()
288                             .setDpId(interfaceInfo.getDpId().toJava())
289                             .setInterfaceName(interfaceInfo.getInterfaceName())
290                             .setLportTag(interfaceInfo.getInterfaceTag()).setSha(port.getMacAddress().getValue())
291                             .setSpa(ip4Address.get()).build();
292                     arpResponderAction.accept(arpResponderInput);
293                 });
294
295     }
296
297 }