2 * Copyright (c) 2016 Ericsson India Global Services Pvt Ltd. and others. All rights reserved.
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
8 package org.opendaylight.netvirt.dhcpservice;
10 import java.math.BigInteger;
11 import java.util.Collections;
12 import java.util.List;
13 import java.util.Locale;
14 import java.util.function.Consumer;
15 import javax.annotation.PostConstruct;
16 import javax.annotation.PreDestroy;
17 import javax.inject.Inject;
18 import javax.inject.Named;
19 import javax.inject.Singleton;
20 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
21 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
22 import org.opendaylight.genius.datastoreutils.AsyncClusteredDataTreeChangeListenerBase;
23 import org.opendaylight.genius.infra.ManagedNewTransactionRunner;
24 import org.opendaylight.genius.infra.ManagedNewTransactionRunnerImpl;
25 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
26 import org.opendaylight.genius.mdsalutil.NwConstants;
27 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
28 import org.opendaylight.netvirt.dhcpservice.api.DhcpMConstants;
29 import org.opendaylight.netvirt.elan.arp.responder.ArpResponderInput;
30 import org.opendaylight.netvirt.elan.arp.responder.ArpResponderInput.ArpReponderInputBuilder;
31 import org.opendaylight.netvirt.elan.arp.responder.ArpResponderUtil;
32 import org.opendaylight.netvirt.elanmanager.api.ElanHelper;
33 import org.opendaylight.netvirt.elanmanager.api.IElanService;
34 import org.opendaylight.netvirt.neutronvpn.api.utils.NeutronConstants;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.binding.rev150712.PortBindingExtension;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.port.attributes.FixedIps;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.Ports;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.Port;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.Subnet;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.dhcpservice.config.rev150710.DhcpserviceConfig;
43 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 public class DhcpNeutronPortListener
49 extends AsyncClusteredDataTreeChangeListenerBase<Port, DhcpNeutronPortListener> {
51 private static final Logger LOG = LoggerFactory.getLogger(DhcpNeutronPortListener.class);
52 private final DhcpExternalTunnelManager dhcpExternalTunnelManager;
53 private final IElanService elanService;
54 private final DataBroker broker;
55 private final ManagedNewTransactionRunner txRunner;
56 private final DhcpserviceConfig config;
57 private final IInterfaceManager interfaceManager;
58 private final JobCoordinator jobCoordinator;
59 private final DhcpManager dhcpManager;
62 public DhcpNeutronPortListener(DataBroker db, DhcpExternalTunnelManager dhcpExternalTunnelManager,
63 @Named("elanService") IElanService ielanService, IInterfaceManager interfaceManager,
64 DhcpserviceConfig config, final JobCoordinator jobCoordinator, DhcpManager dhcpManager) {
65 super(Port.class, DhcpNeutronPortListener.class);
66 this.dhcpExternalTunnelManager = dhcpExternalTunnelManager;
67 this.elanService = ielanService;
68 this.interfaceManager = interfaceManager;
70 this.txRunner = new ManagedNewTransactionRunnerImpl(db);
72 this.jobCoordinator = jobCoordinator;
73 this.dhcpManager = dhcpManager;
78 if (config.isControllerDhcpEnabled()) {
79 registerListener(LogicalDatastoreType.CONFIGURATION, broker);
84 protected InstanceIdentifier<Port> getWildCardPath() {
85 return InstanceIdentifier.create(Neutron.class).child(Ports.class).child(Port.class);
92 LOG.debug("DhcpNeutronPortListener Listener Closed");
96 protected void remove(InstanceIdentifier<Port> identifier, Port del) {
97 LOG.trace("Port removed: {}", del);
98 if (NeutronConstants.IS_ODL_DHCP_PORT.test(del)) {
99 jobCoordinator.enqueueJob(getJobKey(del),
100 () -> Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(tx -> {
101 java.util.Optional<String> ip4Address = DhcpServiceUtils.getIpV4Address(del);
102 if (ip4Address.isPresent()) {
103 dhcpExternalTunnelManager.addOrRemoveDhcpArpFlowforElan(del.getNetworkId().getValue(),
104 false, ip4Address.get(), del.getMacAddress().getValue());
106 DhcpServiceUtils.removeSubnetDhcpPortData(del, subnetDhcpPortIdfr -> tx
107 .delete(LogicalDatastoreType.CONFIGURATION, subnetDhcpPortIdfr));
108 processArpResponderForElanDpns(del, arpInput -> {
109 LOG.trace("Removing ARPResponder Flows for dhcp port {} with ipaddress {} with mac {} "
110 + " on dpn {}. ",arpInput.getInterfaceName(), arpInput.getSpa(),
111 arpInput.getSha(), arpInput.getDpId());
112 elanService.removeArpResponderFlow(arpInput);
116 if (isVnicTypeDirectOrMacVtap(del)) {
121 private String getJobKey(Port port) {
122 return "PORT- " + port.getUuid().getValue();
126 protected void update(InstanceIdentifier<Port> identifier, Port original, Port update) {
127 LOG.trace("Port changed to {}", update);
128 //With Ipv6 changes we can get ipv4 subnets later. The below check is to support such scenario.
129 if (original.getFixedIps().size() < update.getFixedIps().size()) {
130 final String interfaceName = update.getUuid().getValue();
131 List<FixedIps> updatedFixedIps = update.getFixedIps();
132 // Need to check only the newly added fixed ip.
133 updatedFixedIps.removeAll(original.getFixedIps());
134 Subnet subnet = dhcpManager.getNeutronSubnet(updatedFixedIps);
135 if (null == subnet || !subnet.isEnableDhcp()) {
136 LOG.trace("Subnet is null/not ipv4 or not enabled {}", subnet);
139 // Binding the DHCP service for an existing port because of subnet change.
140 jobCoordinator.enqueueJob(DhcpServiceUtils.getJobKey(interfaceName),
141 () -> Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(tx -> {
142 LOG.debug("Binding DHCP service for interface {}", interfaceName);
143 DhcpServiceUtils.bindDhcpService(interfaceName, NwConstants.DHCP_TABLE, tx);
144 })), DhcpMConstants.RETRY_COUNT);
145 jobCoordinator.enqueueJob(DhcpServiceUtils.getJobKey(interfaceName), () -> {
146 BigInteger dpnId = interfaceManager.getDpnForInterface(interfaceName);
147 if (dpnId == null || dpnId.equals(DhcpMConstants.INVALID_DPID)) {
148 LOG.trace("Unable to install the DHCP flow since dpn is not available");
149 return Collections.emptyList();
151 String vmMacAddress = DhcpServiceUtils.getAndUpdateVmMacAddress(broker, interfaceName, dhcpManager);
152 return Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(
153 tx -> dhcpManager.installDhcpEntries(dpnId, vmMacAddress, tx)));
154 }, DhcpMConstants.RETRY_COUNT);
156 if (!isVnicTypeDirectOrMacVtap(update)) {
157 LOG.trace("Port updated is normal {}", update.getUuid());
158 if (isVnicTypeDirectOrMacVtap(original)) {
159 LOG.trace("Original Port was direct/macvtap {} so removing flows and cache entry if any",
161 removePort(original);
165 if (!isVnicTypeDirectOrMacVtap(original)) {
166 LOG.trace("Original port was normal and updated is direct. Calling addPort()");
170 String macOriginal = getMacAddress(original);
171 String macUpdated = getMacAddress(update);
172 String segmentationIdOriginal = DhcpServiceUtils.getSegmentationId(original.getNetworkId(), broker);
173 String segmentationIdUpdated = DhcpServiceUtils.getSegmentationId(update.getNetworkId(), broker);
174 if (macOriginal != null && !macOriginal.equalsIgnoreCase(macUpdated) && segmentationIdOriginal != null
175 && !segmentationIdOriginal.equalsIgnoreCase(segmentationIdUpdated)) {
176 LOG.trace("Mac/segment id has changed");
177 dhcpExternalTunnelManager.removeVniMacToPortCache(new BigInteger(segmentationIdOriginal), macOriginal);
178 dhcpExternalTunnelManager.updateVniMacToPortCache(new BigInteger(segmentationIdUpdated),
184 protected void add(InstanceIdentifier<Port> identifier, Port add) {
185 LOG.trace("Port added {}", add);
186 if (NeutronConstants.IS_ODL_DHCP_PORT.test(add)) {
187 jobCoordinator.enqueueJob(getJobKey(add),
188 () -> Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(tx -> {
189 DhcpServiceUtils.createSubnetDhcpPortData(add, (subnetDhcpPortIdfr, subnetToDhcpport) -> tx
190 .put(LogicalDatastoreType.CONFIGURATION, subnetDhcpPortIdfr, subnetToDhcpport));
191 processArpResponderForElanDpns(add, arpInput -> {
193 "Installing ARP RESPONDER Flows for dhcp port {} ipaddress {} with mac {} on dpn {}",
194 arpInput.getInterfaceName(), arpInput.getSpa(), arpInput.getSha(),
196 ArpReponderInputBuilder builder = new ArpReponderInputBuilder(arpInput);
197 builder.setInstructions(ArpResponderUtil.getInterfaceInstructions(interfaceManager,
198 arpInput.getInterfaceName(), arpInput.getSpa(), arpInput.getSha()));
199 elanService.addArpResponderFlow(builder.buildForInstallFlow());
202 java.util.Optional<String> ip4Address = DhcpServiceUtils.getIpV4Address(add);
203 if (ip4Address.isPresent()) {
204 dhcpExternalTunnelManager.addOrRemoveDhcpArpFlowforElan(add.getNetworkId().getValue(),
205 true, ip4Address.get(), add.getMacAddress().getValue());
208 if (!isVnicTypeDirectOrMacVtap(add)) {
214 private void removePort(Port port) {
215 String macAddress = getMacAddress(port);
216 Uuid networkId = port.getNetworkId();
217 String segmentationId = DhcpServiceUtils.getSegmentationId(networkId, broker);
218 if (segmentationId == null) {
221 List<BigInteger> listOfDpns = DhcpServiceUtils.getListOfDpns(broker);
222 dhcpExternalTunnelManager.unInstallDhcpFlowsForVms(networkId.getValue(), listOfDpns, macAddress);
223 dhcpExternalTunnelManager.removeVniMacToPortCache(new BigInteger(segmentationId), macAddress);
226 private void addPort(Port port) {
227 String macAddress = getMacAddress(port);
228 Uuid networkId = port.getNetworkId();
229 String segmentationId = DhcpServiceUtils.getSegmentationId(networkId, broker);
230 if (segmentationId == null) {
231 LOG.trace("segmentation id is null");
234 dhcpExternalTunnelManager.updateVniMacToPortCache(new BigInteger(segmentationId), macAddress, port);
238 private String getMacAddress(Port port) {
239 return port.getMacAddress().getValue();
242 private boolean isVnicTypeDirectOrMacVtap(Port port) {
243 PortBindingExtension portBinding = port.getAugmentation(PortBindingExtension.class);
244 if (portBinding == null || portBinding.getVnicType() == null) {
245 // By default, VNIC_TYPE is NORMAL
248 String vnicType = portBinding.getVnicType().trim().toLowerCase(Locale.getDefault());
249 return vnicType.equals("direct") || vnicType.equals("macvtap");
253 protected DhcpNeutronPortListener getDataTreeChangeListener() {
254 return DhcpNeutronPortListener.this;
258 * Handle(Add/Remove) ARP Responder for DHCP IP on all the DPNs when DHCP is
259 * enabled/disabled on subnet add or update or delete.
262 * DHCP port for which ARP Responder flow to be added when dhcp
263 * flag is enabled on the subnet or DHCP port for which ARP
264 * Responder flow to be removed when dhcp flag is disabled on the
266 * @param arpResponderAction
267 * ARP Responder Action to be performed i.e., add or remove flow
269 private void processArpResponderForElanDpns(Port port, Consumer<ArpResponderInput> arpResponderAction) {
271 java.util.Optional<String> ip4Address = DhcpServiceUtils.getIpV4Address(port);
272 if (!ip4Address.isPresent()) {
273 LOG.warn("There is no IPv4Address for port {}, not performing ARP responder add/remove flow operation",
277 ElanHelper.getDpnInterfacesInElanInstance(broker, port.getNetworkId().getValue()).stream()
278 .map(ifName -> DhcpServiceUtils.getInterfaceInfo(interfaceManager, ifName)).forEach(interfaceInfo -> {
279 ArpResponderInput arpResponderInput = new ArpResponderInput.ArpReponderInputBuilder()
280 .setDpId(interfaceInfo.getDpId()).setInterfaceName(interfaceInfo.getInterfaceName())
281 .setLportTag(interfaceInfo.getInterfaceTag()).setSha(port.getMacAddress().getValue())
282 .setSpa(ip4Address.get()).build();
283 arpResponderAction.accept(arpResponderInput);