2 * Copyright © 2015, 2018 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.neutronvpn;
10 import static org.opendaylight.genius.infra.Datastore.CONFIGURATION;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Strings;
14 import com.google.gson.Gson;
15 import com.google.gson.JsonArray;
16 import com.google.gson.JsonElement;
17 import com.google.gson.JsonObject;
18 import java.time.Duration;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Locale;
27 import java.util.stream.Collectors;
28 import javax.annotation.PostConstruct;
29 import javax.inject.Singleton;
30 import org.apache.commons.lang3.ObjectUtils;
31 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
32 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
33 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
34 import org.opendaylight.genius.datastoreutils.AsyncDataTreeChangeListenerBase;
35 import org.opendaylight.genius.datastoreutils.SingleTransactionDataBroker;
36 import org.opendaylight.genius.datastoreutils.listeners.DataTreeEventCallbackRegistrar;
37 import org.opendaylight.genius.infra.Datastore;
38 import org.opendaylight.genius.infra.ManagedNewTransactionRunner;
39 import org.opendaylight.genius.infra.ManagedNewTransactionRunnerImpl;
40 import org.opendaylight.genius.infra.TypedWriteTransaction;
41 import org.opendaylight.genius.mdsalutil.MDSALUtil;
42 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
43 import org.opendaylight.infrautils.utils.concurrent.ListenableFutures;
44 import org.opendaylight.netvirt.elanmanager.api.IElanService;
45 import org.opendaylight.netvirt.neutronvpn.api.enums.IpVersionChoice;
46 import org.opendaylight.netvirt.neutronvpn.api.utils.NeutronConstants;
47 import org.opendaylight.netvirt.neutronvpn.api.utils.NeutronUtils;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev140508.L2vlan;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.InterfaceBuilder;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.IfL2vlan;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.IfL2vlanBuilder;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.InterfaceAcl;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.InterfaceAclBuilder;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.interfaces._interface.AllowedAddressPairs;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.ElanInterfaces;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.interfaces.ElanInterface;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.interfaces.ElanInterfaceBuilder;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.interfaces.ElanInterfaceKey;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.interfaces.elan._interface.StaticMacEntries;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ext.routers.Routers;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ext.routers.RoutersBuilder;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.floating.ip.port.info.FloatingIpIdToPortMappingBuilder;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.floating.ip.port.info.FloatingIpIdToPortMappingKey;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.subnetmaps.Subnetmap;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.binding.rev150712.PortBindingExtension;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.hostconfig.rev150712.hostconfig.attributes.hostconfigs.Hostconfig;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.Router;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.networks.rev150712.networks.attributes.networks.Network;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.port.attributes.FixedIps;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.Ports;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.Port;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
75 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
80 public class NeutronPortChangeListener extends AsyncDataTreeChangeListenerBase<Port, NeutronPortChangeListener> {
81 private static final Logger LOG = LoggerFactory.getLogger(NeutronPortChangeListener.class);
82 private final DataBroker dataBroker;
83 private final ManagedNewTransactionRunner txRunner;
84 private final NeutronvpnManager nvpnManager;
85 private final NeutronvpnNatManager nvpnNatManager;
86 private final NeutronSubnetGwMacResolver gwMacResolver;
87 private final IElanService elanService;
88 private final JobCoordinator jobCoordinator;
89 private final NeutronvpnUtils neutronvpnUtils;
90 private final HostConfigCache hostConfigCache;
91 private final DataTreeEventCallbackRegistrar eventCallbacks;
93 public NeutronPortChangeListener(final DataBroker dataBroker,
94 final NeutronvpnManager neutronvpnManager,
95 final NeutronvpnNatManager neutronvpnNatManager,
96 final NeutronSubnetGwMacResolver gwMacResolver,
97 final IElanService elanService,
98 final JobCoordinator jobCoordinator,
99 final NeutronvpnUtils neutronvpnUtils,
100 final HostConfigCache hostConfigCache,
101 final DataTreeEventCallbackRegistrar dataTreeEventCallbackRegistrar) {
102 super(Port.class, NeutronPortChangeListener.class);
103 this.dataBroker = dataBroker;
104 this.txRunner = new ManagedNewTransactionRunnerImpl(dataBroker);
105 nvpnManager = neutronvpnManager;
106 nvpnNatManager = neutronvpnNatManager;
107 this.gwMacResolver = gwMacResolver;
108 this.elanService = elanService;
109 this.jobCoordinator = jobCoordinator;
110 this.neutronvpnUtils = neutronvpnUtils;
111 this.hostConfigCache = hostConfigCache;
112 this.eventCallbacks = dataTreeEventCallbackRegistrar;
118 LOG.info("{} init", getClass().getSimpleName());
119 registerListener(LogicalDatastoreType.CONFIGURATION, dataBroker);
123 protected InstanceIdentifier<Port> getWildCardPath() {
124 return InstanceIdentifier.create(Neutron.class).child(Ports.class).child(Port.class);
128 protected NeutronPortChangeListener getDataTreeChangeListener() {
129 return NeutronPortChangeListener.this;
134 protected void add(InstanceIdentifier<Port> identifier, Port input) {
135 String portName = input.getUuid().getValue();
136 LOG.trace("Adding Port : key: {}, value={}", identifier, input);
137 Network network = neutronvpnUtils.getNeutronNetwork(input.getNetworkId());
138 if (network == null || !NeutronvpnUtils.isNetworkTypeSupported(network)) {
139 LOG.warn("neutron vpn received a port add() for a network without a provider extension augmentation "
140 + "or with an unsupported network type for the port {} which is part of network {}",
145 neutronvpnUtils.addToPortCache(input);
146 String portStatus = NeutronUtils.PORT_STATUS_DOWN;
147 if (!Strings.isNullOrEmpty(input.getDeviceOwner()) && !Strings.isNullOrEmpty(input.getDeviceId())) {
148 if (NeutronConstants.DEVICE_OWNER_ROUTER_INF.equals(input.getDeviceOwner())) {
149 handleRouterInterfaceAdded(input);
150 NeutronUtils.createPortStatus(input.getUuid().getValue(), NeutronUtils.PORT_STATUS_ACTIVE, dataBroker);
153 if (NeutronConstants.DEVICE_OWNER_GATEWAY_INF.equals(input.getDeviceOwner())) {
154 handleRouterGatewayUpdated(input, false);
155 portStatus = NeutronUtils.PORT_STATUS_ACTIVE;
156 } else if (NeutronConstants.DEVICE_OWNER_FLOATING_IP.equals(input.getDeviceOwner())) {
157 handleFloatingIpPortUpdated(null, input);
158 portStatus = NeutronUtils.PORT_STATUS_ACTIVE;
161 // Switchdev ports need to be bounded to a host before creation
162 // in order to validate the supported vnic types from the hostconfig
163 if (input.getFixedIps() != null
164 && !input.getFixedIps().isEmpty()
165 && !(isPortTypeSwitchdev(input) && !isPortBound(input))) {
166 handleNeutronPortCreated(input);
168 NeutronUtils.createPortStatus(input.getUuid().getValue(), portStatus, dataBroker);
172 protected void remove(InstanceIdentifier<Port> identifier, Port input) {
173 LOG.trace("Removing Port : key: {}, value={}", identifier, input);
174 Network network = neutronvpnUtils.getNeutronNetwork(input.getNetworkId());
175 // need to proceed with deletion in case network is null for a case where v2 sync happens and a read for
176 // network from NN returns null, but the deletion process for port needs to continue
177 if (network != null && !NeutronvpnUtils.isNetworkTypeSupported(network)) {
178 String portName = input.getUuid().getValue();
179 LOG.warn("neutron vpn received a port remove() for a network without a provider extension augmentation "
180 + "or with an unsupported network type for the port {} which is part of network {}",
184 neutronvpnUtils.removeFromPortCache(input);
185 NeutronUtils.deletePortStatus(input.getUuid().getValue(), dataBroker);
187 if (!Strings.isNullOrEmpty(input.getDeviceOwner()) && !Strings.isNullOrEmpty(input.getDeviceId())) {
188 if (NeutronConstants.DEVICE_OWNER_ROUTER_INF.equals(input.getDeviceOwner())) {
189 handleRouterInterfaceRemoved(input);
190 /* nothing else to do here */
192 } else if (NeutronConstants.DEVICE_OWNER_GATEWAY_INF.equals(input.getDeviceOwner())
193 || NeutronConstants.DEVICE_OWNER_FLOATING_IP.equals(input.getDeviceOwner())) {
194 handleRouterGatewayUpdated(input, true);
195 elanService.removeKnownL3DmacAddress(input.getMacAddress().getValue(), input.getNetworkId().getValue());
198 if (input.getFixedIps() != null) {
199 handleNeutronPortDeleted(input);
204 protected void update(InstanceIdentifier<Port> identifier, Port original, Port update) {
205 // Switchdev ports need to be bounded to a host before creation
206 // in order to validate the supported vnic types from the hostconfig
207 if (isPortTypeSwitchdev(original)
208 && !isPortBound(original)
209 && isPortBound(update)) {
210 handleNeutronPortCreated(update);
212 final String portName = update.getUuid().getValue();
213 Network network = neutronvpnUtils.getNeutronNetwork(update.getNetworkId());
214 LOG.info("Update port {} from network {}", portName, update.getNetworkId().toString());
215 if (network == null || !NeutronvpnUtils.isNetworkTypeSupported(network)) {
216 LOG.warn("neutron vpn received a port update() for a network without a provider extension augmentation "
217 + "or with an unsupported network type for the port {} which is part of network {}",
221 neutronvpnUtils.addToPortCache(update);
223 if ((Strings.isNullOrEmpty(original.getDeviceOwner()) || Strings.isNullOrEmpty(original.getDeviceId())
224 || NeutronConstants.FLOATING_IP_DEVICE_ID_PENDING.equalsIgnoreCase(original.getDeviceId()))
225 && !Strings.isNullOrEmpty(update.getDeviceOwner()) && !Strings.isNullOrEmpty(update.getDeviceId())) {
226 if (NeutronConstants.DEVICE_OWNER_ROUTER_INF.equals(update.getDeviceOwner())) {
227 handleRouterInterfaceAdded(update);
230 if (NeutronConstants.DEVICE_OWNER_GATEWAY_INF.equals(update.getDeviceOwner())) {
231 handleRouterGatewayUpdated(update, false);
232 } else if (NeutronConstants.DEVICE_OWNER_FLOATING_IP.equals(update.getDeviceOwner())) {
233 handleFloatingIpPortUpdated(original, update);
236 Set<FixedIps> oldIPs = getFixedIpSet(original.getFixedIps());
237 Set<FixedIps> newIPs = getFixedIpSet(update.getFixedIps());
238 if (!oldIPs.equals(newIPs)) {
239 handleNeutronPortUpdated(original, update);
243 // check if port security enabled/disabled as part of port update
244 boolean origSecurityEnabled = NeutronvpnUtils.getPortSecurityEnabled(original);
245 boolean updatedSecurityEnabled = NeutronvpnUtils.getPortSecurityEnabled(update);
247 if (origSecurityEnabled || updatedSecurityEnabled) {
248 InstanceIdentifier<Interface> interfaceIdentifier = NeutronvpnUtils.buildVlanInterfaceIdentifier(portName);
249 jobCoordinator.enqueueJob("PORT- " + portName,
250 () -> Collections.singletonList(txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION,
252 Optional<Interface> optionalInf =
253 confTx.read(interfaceIdentifier).get();
254 if (optionalInf.isPresent()) {
255 InterfaceBuilder interfaceBuilder = new InterfaceBuilder(optionalInf.get());
256 InterfaceAcl infAcl = handlePortSecurityUpdated(original, update,
257 origSecurityEnabled, updatedSecurityEnabled, interfaceBuilder).build();
258 interfaceBuilder.addAugmentation(InterfaceAcl.class, infAcl);
259 LOG.info("update: Of-port-interface updation for port {}", portName);
260 // Update OFPort interface for this neutron port
261 confTx.put(interfaceIdentifier, interfaceBuilder.build());
263 LOG.warn("update: Interface {} is not present", portName);
269 private void handleFloatingIpPortUpdated(Port original, Port update) {
270 if ((original == null || original.getDeviceId().equals(NeutronConstants.FLOATING_IP_DEVICE_ID_PENDING))
271 && !update.getDeviceId().equals(NeutronConstants.FLOATING_IP_DEVICE_ID_PENDING)) {
272 // populate floating-ip uuid and floating-ip port attributes (uuid, mac and subnet id for the ONLY
273 // fixed IP) to be used by NAT, depopulated in NATService once mac is retrieved in the removal path
274 addToFloatingIpPortInfo(new Uuid(update.getDeviceId()), update.getUuid(), update.getFixedIps().get(0)
275 .getSubnetId(), update.getMacAddress().getValue());
276 elanService.addKnownL3DmacAddress(update.getMacAddress().getValue(), update.getNetworkId().getValue());
280 private void handleRouterInterfaceAdded(Port routerPort) {
281 if (routerPort.getDeviceId() != null) {
282 Uuid routerId = new Uuid(routerPort.getDeviceId());
283 Uuid infNetworkId = routerPort.getNetworkId();
284 Uuid existingVpnId = neutronvpnUtils.getVpnForNetwork(infNetworkId);
286 elanService.addKnownL3DmacAddress(routerPort.getMacAddress().getValue(), infNetworkId.getValue());
287 if (existingVpnId == null) {
288 Set<Uuid> listVpnIds = new HashSet<>();
289 Uuid vpnId = neutronvpnUtils.getVpnForRouter(routerId, true);
293 listVpnIds.add(vpnId);
294 Uuid internetVpnId = neutronvpnUtils.getInternetvpnUuidBoundToRouterId(routerId);
295 List<Subnetmap> subnetMapList = new ArrayList<>();
296 List<FixedIps> portIps = routerPort.getFixedIps();
297 boolean portIsIpv6 = false;
298 for (FixedIps portIP : portIps) {
299 // NOTE: Please donot change the order of calls to updateSubnetNodeWithFixedIP
300 // and addSubnetToVpn here
301 if (internetVpnId != null
302 && portIP.getIpAddress().getIpv6Address() != null) {
305 String ipValue = portIP.getIpAddress().stringValue();
306 Uuid subnetId = portIP.getSubnetId();
307 nvpnManager.updateSubnetNodeWithFixedIp(subnetId, routerId,
308 routerPort.getUuid(), ipValue, routerPort.getMacAddress().getValue(), vpnId);
309 Subnetmap sn = neutronvpnUtils.getSubnetmap(subnetId);
310 subnetMapList.add(sn);
313 listVpnIds.add(internetVpnId);
314 if (neutronvpnUtils.shouldVpnHandleIpVersionChoiceChange(
315 IpVersionChoice.IPV6, routerId, true)) {
316 neutronvpnUtils.updateVpnInstanceWithIpFamily(internetVpnId.getValue(), IpVersionChoice.IPV6,
318 neutronvpnUtils.updateVpnInstanceWithFallback(internetVpnId, true);
321 if (! subnetMapList.isEmpty()) {
322 nvpnManager.createVpnInterface(listVpnIds, routerPort, null);
324 IpVersionChoice ipVersion = IpVersionChoice.UNDEFINED;
325 for (FixedIps portIP : routerPort.getFixedIps()) {
326 String ipValue = portIP.getIpAddress().stringValue();
327 ipVersion = NeutronvpnUtils.getIpVersionFromString(ipValue);
328 if (ipVersion.isIpVersionChosen(IpVersionChoice.IPV4)) {
329 nvpnManager.addSubnetToVpn(vpnId, portIP.getSubnetId(),
330 null /* internet-vpn-id */);
332 nvpnManager.addSubnetToVpn(vpnId, portIP.getSubnetId(), internetVpnId);
334 LOG.trace("NeutronPortChangeListener Add Subnet Gateway IP {} MAC {} Interface {} VPN {}",
335 ipValue, routerPort.getMacAddress(),
336 routerPort.getUuid().getValue(), vpnId.getValue());
338 if (neutronvpnUtils.shouldVpnHandleIpVersionChoiceChange(ipVersion, routerId, true)) {
339 LOG.debug("vpnInstanceOpDataEntry is getting update with ip address family {} for VPN {}",
340 ipVersion, vpnId.getValue());
341 neutronvpnUtils.updateVpnInstanceWithIpFamily(vpnId.getValue(), ipVersion, true);
343 nvpnManager.addToNeutronRouterInterfacesMap(routerId, routerPort.getUuid().getValue());
344 jobCoordinator.enqueueJob(routerId.toString(), () -> {
345 nvpnNatManager.handleSubnetsForExternalRouter(routerId);
346 return Collections.emptyList();
348 ListenableFutures.addErrorLogging(txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION,
350 String portInterfaceName = createOfPortInterface(routerPort, confTx);
351 createElanInterface(routerPort, portInterfaceName, confTx);
352 }), LOG, "Error creating ELAN interface for {}", routerPort);
354 LOG.error("Neutron network {} corresponding to router interface port {} for neutron router {}"
355 + " already associated to VPN {}", infNetworkId.getValue(), routerPort.getUuid().getValue(),
356 routerId.getValue(), existingVpnId.getValue());
361 private void handleRouterInterfaceRemoved(Port routerPort) {
362 if (routerPort.getDeviceId() != null) {
363 Uuid routerId = new Uuid(routerPort.getDeviceId());
364 Uuid infNetworkId = routerPort.getNetworkId();
365 elanService.removeKnownL3DmacAddress(routerPort.getMacAddress().getValue(), infNetworkId.getValue());
366 Uuid vpnId = ObjectUtils.defaultIfNull(neutronvpnUtils.getVpnForRouter(routerId, true),
368 List<FixedIps> portIps = routerPort.getFixedIps();
369 boolean vpnInstanceInternetIpVersionRemoved = false;
370 Uuid vpnInstanceInternetUuid = null;
371 for (FixedIps portIP : portIps) {
372 // Internet VPN : flush InternetVPN first
373 Uuid subnetId = portIP.getSubnetId();
374 Subnetmap sn = neutronvpnUtils.getSubnetmap(subnetId);
375 if (sn != null && sn.getInternetVpnId() != null) {
376 if (neutronvpnUtils.shouldVpnHandleIpVersionChangeToRemove(sn, sn.getInternetVpnId())) {
377 vpnInstanceInternetIpVersionRemoved = true;
378 vpnInstanceInternetUuid = sn.getInternetVpnId();
380 nvpnManager.updateVpnInternetForSubnet(sn, sn.getInternetVpnId(), false);
383 /* Remove ping responder for router interfaces
384 * A router interface reference in a VPN will have to be removed before the host interface references
385 * for that subnet in the VPN are removed. This is to ensure that the FIB Entry of the router interface
386 * is not the last entry to be removed for that subnet in the VPN.
387 * If router interface FIB entry is the last to be removed for a subnet in a VPN , then all the host
388 * interface references in the vpn will already have been cleared, which will cause failures in
389 * cleanup of router interface flows*/
390 nvpnManager.deleteVpnInterface(routerPort.getUuid().getValue(),
391 null /* vpn-id */, null /* wrtConfigTxn*/);
392 final Uuid internetVpnId = vpnInstanceInternetUuid;
393 // update RouterInterfaces map
394 ListenableFutures.addErrorLogging(txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION,
396 IpVersionChoice ipVersion = IpVersionChoice.UNDEFINED;
397 for (FixedIps portIP : portIps) {
398 Subnetmap sn = neutronvpnUtils.getSubnetmap(portIP.getSubnetId());
399 // router Port have either IPv4 or IPv6, never both
400 ipVersion = neutronvpnUtils.getIpVersionFromString(sn.getSubnetIp());
401 String ipValue = portIP.getIpAddress().stringValue();
402 neutronvpnUtils.removeVpnPortFixedIpToPort(vpnId.getValue(), ipValue, confTx);
403 // NOTE: Please donot change the order of calls to removeSubnetFromVpn and
404 // and updateSubnetNodeWithFixedIP
405 nvpnManager.removeSubnetFromVpn(vpnId, portIP.getSubnetId(), internetVpnId);
406 nvpnManager.updateSubnetNodeWithFixedIp(portIP.getSubnetId(), null, null,
409 nvpnManager.removeFromNeutronRouterInterfacesMap(routerId, routerPort.getUuid().getValue());
410 deleteElanInterface(routerPort.getUuid().getValue(), confTx);
411 deleteOfPortInterface(routerPort, confTx);
412 jobCoordinator.enqueueJob(routerId.toString(), () -> {
413 nvpnNatManager.handleSubnetsForExternalRouter(routerId);
414 return Collections.emptyList();
416 if (neutronvpnUtils.shouldVpnHandleIpVersionChoiceChange(ipVersion, routerId, false)) {
417 LOG.debug("vpnInstanceOpDataEntry is getting update with ip address family {} for VPN {}",
418 ipVersion, vpnId.getValue());
419 neutronvpnUtils.updateVpnInstanceWithIpFamily(vpnId.getValue(), ipVersion, false);
421 }), LOG, "Error handling interface removal");
422 if (vpnInstanceInternetIpVersionRemoved) {
423 neutronvpnUtils.updateVpnInstanceWithIpFamily(vpnInstanceInternetUuid.getValue(),
424 IpVersionChoice.IPV6, false);
425 neutronvpnUtils.updateVpnInstanceWithFallback(vpnInstanceInternetUuid, false);
430 private void handleRouterGatewayUpdated(Port routerGwPort, boolean isRtrGwRemoved) {
431 Uuid routerId = new Uuid(routerGwPort.getDeviceId());
432 Uuid networkId = routerGwPort.getNetworkId();
433 Network network = neutronvpnUtils.getNeutronNetwork(networkId);
434 if (network == null) {
437 boolean isExternal = NeutronvpnUtils.getIsExternal(network);
439 Uuid vpnInternetId = neutronvpnUtils.getVpnForNetwork(networkId);
440 if (vpnInternetId != null) {
441 List<Subnetmap> snList = neutronvpnUtils.getNeutronRouterSubnetMaps(routerId);
442 for (Subnetmap sn : snList) {
443 if (sn.getNetworkId() == networkId) {
446 if (NeutronvpnUtils.getIpVersionFromString(sn.getSubnetIp()) != IpVersionChoice.IPV6) {
449 if (isRtrGwRemoved) {
450 nvpnManager.removeV6PrivateSubnetToExtNetwork(vpnInternetId, sn);
452 nvpnManager.addV6PrivateSubnetToExtNetwork(vpnInternetId, sn);
455 //update VPN Maps with extRouterId in InternetBgpVpn
456 if (isRtrGwRemoved) {
457 nvpnManager.updateVpnMaps(vpnInternetId, null, null, null, null);
459 nvpnManager.updateVpnMaps(vpnInternetId, null, routerId, null, null);
463 elanService.addKnownL3DmacAddress(routerGwPort.getMacAddress().getValue(), networkId.getValue());
465 Router router = neutronvpnUtils.getNeutronRouter(routerId);
466 if (router == null) {
467 LOG.warn("No router found for router GW port {} for router {}", routerGwPort.getUuid().getValue(),
468 routerId.getValue());
470 eventCallbacks.onAddOrUpdate(LogicalDatastoreType.CONFIGURATION,
471 neutronvpnUtils.getNeutronRouterIid(routerId), (unused, newRouter) -> {
472 setupGwMac(newRouter, routerGwPort, routerId);
473 return DataTreeEventCallbackRegistrar.NextAction.UNREGISTER;
474 }, Duration.ofSeconds(3), iid -> {
475 LOG.error("GwPort {} added without Router", routerGwPort.getUuid().getValue());
479 setupGwMac(router, routerGwPort, routerId);
482 private void setupGwMac(Router router, Port routerGwPort, Uuid routerId) {
483 gwMacResolver.sendArpRequestsToExtGateways(router);
484 jobCoordinator.enqueueJob(routerId.toString(), () -> {
485 setExternalGwMac(routerGwPort, routerId);
486 return Collections.emptyList();
490 private void setExternalGwMac(Port routerGwPort, Uuid routerId) {
491 // During full-sync networking-odl syncs routers before ports. As such,
492 // the MAC of the router's gw port is not available to be set when the
493 // router is written. We catch that here.
494 InstanceIdentifier<Routers> routersId = NeutronvpnUtils.buildExtRoutersIdentifier(routerId);
495 Optional<Routers> optionalRouter = MDSALUtil.read(dataBroker, LogicalDatastoreType.CONFIGURATION, routersId);
496 if (!optionalRouter.isPresent()) {
500 Routers extRouters = optionalRouter.get();
501 if (extRouters.getExtGwMacAddress() != null) {
505 RoutersBuilder builder = new RoutersBuilder(extRouters);
506 builder.setExtGwMacAddress(routerGwPort.getMacAddress().getValue());
507 MDSALUtil.syncWrite(dataBroker, LogicalDatastoreType.CONFIGURATION, routersId, builder.build());
510 private String getPortHostId(final Port port) {
512 PortBindingExtension portBinding = port.augmentation(PortBindingExtension.class);
513 if (portBinding != null) {
514 return portBinding.getHostId();
520 private Hostconfig getHostConfig(final Port port) {
521 String hostId = getPortHostId(port);
522 if (hostId == null) {
525 Optional<Hostconfig> hostConfig;
527 hostConfig = this.hostConfigCache.get(hostId);
528 } catch (ReadFailedException e) {
529 LOG.error("failed to read host config from host {}", hostId, e);
532 return hostConfig.isPresent() ? hostConfig.get() : null;
535 private boolean isPortBound(final Port port) {
536 String hostId = getPortHostId(port);
537 return hostId != null && !hostId.isEmpty();
540 private boolean isPortVnicTypeDirect(Port port) {
541 PortBindingExtension portBinding = port.augmentation(PortBindingExtension.class);
542 if (portBinding == null || portBinding.getVnicType() == null) {
543 // By default, VNIC_TYPE is NORMAL
546 String vnicType = portBinding.getVnicType().trim().toLowerCase(Locale.getDefault());
547 return vnicType.equals(NeutronConstants.VNIC_TYPE_DIRECT);
550 private boolean isSupportedVnicTypeByHost(final Port port, final String vnicType) {
551 Hostconfig hostConfig = getHostConfig(port);
552 String supportStr = String.format("\"vnic_type\": \"%s\"", vnicType);
553 if (hostConfig != null && hostConfig.getConfig().contains(supportStr)) {
559 private Map<String, JsonElement> unmarshal(final String profile) {
560 if (null == profile) {
563 Gson gson = new Gson();
564 JsonObject jsonObject = gson.fromJson(profile, JsonObject.class);
565 Map<String, JsonElement> map = new HashMap();
566 for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
567 map.put(entry.getKey(), entry.getValue());
572 private boolean isPortTypeSwitchdev(final Port port) {
573 if (!isPortVnicTypeDirect(port)) {
577 PortBindingExtension portBinding = port.augmentation(PortBindingExtension.class);
578 String profile = portBinding.getProfile();
579 if (profile == null || profile.isEmpty()) {
580 LOG.debug("Port {} has no binding:profile values", port.getUuid());
584 Map<String, JsonElement> mapProfile = unmarshal(profile);
585 JsonElement capabilities = mapProfile.get(NeutronConstants.BINDING_PROFILE_CAPABILITIES);
586 LOG.debug("Port {} capabilities: {}", port.getUuid(), capabilities);
587 if (capabilities == null || !capabilities.isJsonArray()) {
588 LOG.debug("binding profile capabilities not in array format: {}", capabilities);
592 JsonArray capabilitiesArray = capabilities.getAsJsonArray();
593 Gson gson = new Gson();
594 JsonElement switchdevElement = gson.fromJson(NeutronConstants.SWITCHDEV, JsonElement.class);
595 return capabilitiesArray.contains(switchdevElement);
599 private void handleNeutronPortCreated(final Port port) {
600 final String portName = port.getUuid().getValue();
601 final Uuid portId = port.getUuid();
602 final List<FixedIps> portIpAddrsList = port.getFixedIps();
603 if (NeutronConstants.IS_ODL_DHCP_PORT.test(port)) {
606 jobCoordinator.enqueueJob("PORT- " + portName, () -> {
607 // add direct port to subnetMaps config DS
608 if (!(NeutronUtils.isPortVnicTypeNormal(port)
609 || isPortTypeSwitchdev(port)
610 && isSupportedVnicTypeByHost(port, NeutronConstants.VNIC_TYPE_DIRECT))) {
611 for (FixedIps ip: portIpAddrsList) {
612 nvpnManager.updateSubnetmapNodeWithPorts(ip.getSubnetId(), null, portId);
614 LOG.info("Port {} is not a normal and not a direct with switchdev VNIC type ;"
615 + "OF Port interfaces are not created", portName);
616 return Collections.emptyList();
618 return Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, tx -> {
619 LOG.info("Of-port-interface creation for port {}", portName);
620 // Create of-port interface for this neutron port
621 String portInterfaceName = createOfPortInterface(port, tx);
622 LOG.debug("Creating ELAN Interface for port {}", portName);
623 createElanInterface(port, portInterfaceName, tx);
624 Set<Uuid> vpnIdList = new HashSet<>();
625 Set<Uuid> routerIds = new HashSet<>();
626 for (FixedIps ip: portIpAddrsList) {
627 Subnetmap subnetMap = nvpnManager.updateSubnetmapNodeWithPorts(ip.getSubnetId(), portId, null);
628 if (subnetMap != null && subnetMap.getInternetVpnId() != null) {
629 if (!vpnIdList.contains(subnetMap.getInternetVpnId())) {
630 vpnIdList.add(subnetMap.getInternetVpnId());
633 if (subnetMap != null && subnetMap.getVpnId() != null) {
634 // can't use NeutronvpnUtils.getVpnForNetwork to optimise here, because it gives BGPVPN id
635 // obtained subnetMaps belongs to one network => vpnId must be the same for each port Ip
636 Uuid vpnId = subnetMap.getVpnId();
638 vpnIdList.add(vpnId);
641 if (subnetMap != null && subnetMap.getRouterId() != null) {
642 routerIds.add(subnetMap.getRouterId());
645 if (!vpnIdList.isEmpty()) {
646 // create new vpn-interface for neutron port
647 LOG.debug("handleNeutronPortCreated: Adding VPN Interface for port {} from network {}", portName,
648 port.getNetworkId().toString());
649 nvpnManager.createVpnInterface(vpnIdList, port, tx);
650 if (!routerIds.isEmpty()) {
651 for (Uuid routerId : routerIds) {
652 nvpnManager.addToNeutronRouterInterfacesMap(routerId,port.getUuid().getValue());
660 private void handleNeutronPortDeleted(final Port port) {
661 final String portName = port.getUuid().getValue();
662 final Uuid portId = port.getUuid();
663 final List<FixedIps> portIpsList = port.getFixedIps();
664 jobCoordinator.enqueueJob("PORT- " + portName,
665 () -> Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, confTx -> {
666 if (!(NeutronUtils.isPortVnicTypeNormal(port) || isPortTypeSwitchdev(port))) {
667 for (FixedIps ip : portIpsList) {
668 // remove direct port from subnetMaps config DS
669 nvpnManager.removePortsFromSubnetmapNode(ip.getSubnetId(), null, portId);
671 LOG.info("Port {} is not a normal and not a direct with switchdev VNIC type ;"
672 + "Skipping OF Port interfaces removal", portName);
676 Set<Uuid> routerIds = new HashSet<>();
677 Uuid internetVpnId = null;
678 for (FixedIps ip : portIpsList) {
679 Subnetmap subnetMap = nvpnManager.removePortsFromSubnetmapNode(ip.getSubnetId(), portId, null);
680 if (subnetMap == null) {
683 if (subnetMap.getVpnId() != null) {
684 // can't use NeutronvpnUtils.getVpnForNetwork to optimise here, because it gives BGPVPN id
685 // obtained subnetMaps belongs to one network => vpnId must be the same for each port Ip
686 vpnId = subnetMap.getVpnId();
688 if (subnetMap.getRouterId() != null) {
689 routerIds.add(subnetMap.getRouterId());
691 internetVpnId = subnetMap.getInternetVpnId();
693 if (NeutronConstants.DEVICE_OWNER_GATEWAY_INF.equals(port.getDeviceOwner())
694 || NeutronConstants.DEVICE_OWNER_FLOATING_IP.equals(port.getDeviceOwner())) {
695 String ipAddress = ip.getIpAddress().stringValue();
697 neutronvpnUtils.removeVpnPortFixedIpToPort(vpnId.getValue(), ipAddress, confTx);
699 if (internetVpnId != null) {
700 neutronvpnUtils.removeVpnPortFixedIpToPort(internetVpnId.getValue(),
705 if (vpnId != null || internetVpnId != null) {
706 // remove vpn-interface for this neutron port
707 LOG.debug("removing VPN Interface for port {}", portName);
708 if (!routerIds.isEmpty()) {
709 for (Uuid routerId : routerIds) {
710 nvpnManager.removeFromNeutronRouterInterfacesMap(routerId, portName);
713 nvpnManager.deleteVpnInterface(portName, null /* vpn-id */, confTx);
715 // Remove of-port interface for this neutron port
716 // ELAN interface is also implicitly deleted as part of this operation
717 LOG.debug("Of-port-interface removal for port {}", portName);
718 deleteOfPortInterface(port, confTx);
719 //dissociate fixedIP from floatingIP if associated
720 nvpnManager.dissociatefixedIPFromFloatingIP(port.getUuid().getValue());
725 private void handleNeutronPortUpdated(final Port portoriginal, final Port portupdate) {
726 final List<FixedIps> portoriginalIps = portoriginal.getFixedIps();
727 final List<FixedIps> portupdateIps = portupdate.getFixedIps();
728 if (portoriginalIps == null || portoriginalIps.isEmpty()) {
729 handleNeutronPortCreated(portupdate);
733 if (portupdateIps == null || portupdateIps.isEmpty()) {
734 LOG.info("Ignoring portUpdate (fixed_ip removal) for port {} as this case is handled "
735 + "during subnet deletion event.", portupdate.getUuid().getValue());
739 if (NeutronConstants.IS_ODL_DHCP_PORT.test(portupdate)) {
743 jobCoordinator.enqueueJob("PORT- " + portupdate.getUuid().getValue(),
744 () -> Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, confTx -> {
745 final List<Uuid> originalSnMapsIds = portoriginalIps.stream().map(FixedIps::getSubnetId)
746 .collect(Collectors.toList());
747 final List<Uuid> updateSnMapsIds = portupdateIps.stream().map(FixedIps::getSubnetId)
748 .collect(Collectors.toList());
749 Set<Uuid> originalRouterIds = new HashSet<>();
750 Set<Uuid> oldVpnIds = new HashSet<>();
751 for (Uuid snId: originalSnMapsIds) {
752 if (!updateSnMapsIds.remove(snId)) {
753 // snId was present in originalSnMapsIds, but not in updateSnMapsIds
754 Subnetmap subnetMapOld = nvpnManager.removePortsFromSubnetmapNode(snId, portoriginal.getUuid(),
756 if (subnetMapOld != null && subnetMapOld.getVpnId() != null) {
757 oldVpnIds.add(subnetMapOld.getVpnId());
759 if (subnetMapOld != null && subnetMapOld.getInternetVpnId() != null) {
760 oldVpnIds.add(subnetMapOld.getInternetVpnId());
762 if (subnetMapOld != null && subnetMapOld.getRouterId() != null) {
763 originalRouterIds.add(subnetMapOld.getRouterId());
767 Set<Uuid> newVpnIds = new HashSet<>();
768 Set<Uuid> newRouterIds = new HashSet<>();
769 for (Uuid snId: updateSnMapsIds) {
770 Subnetmap subnetMapNew = nvpnManager.updateSubnetmapNodeWithPorts(snId, portupdate.getUuid(), null);
771 if (subnetMapNew != null) {
772 if (subnetMapNew.getVpnId() != null) {
773 newVpnIds.add(subnetMapNew.getVpnId());
775 if (subnetMapNew.getInternetVpnId() != null) {
776 newVpnIds.add(subnetMapNew.getInternetVpnId());
778 if (subnetMapNew.getRouterId() != null) {
779 newRouterIds.add(subnetMapNew.getRouterId());
783 if (!oldVpnIds.isEmpty()) {
784 LOG.info("removing VPN Interface for port {}", portoriginal.getUuid().getValue());
785 if (!originalRouterIds.isEmpty()) {
786 for (Uuid routerId : originalRouterIds) {
787 nvpnManager.removeFromNeutronRouterInterfacesMap(routerId,
788 portoriginal.getUuid().getValue());
791 nvpnManager.deleteVpnInterface(portoriginal.getUuid().getValue(),
792 null /* vpn-id */, confTx);
794 if (!newVpnIds.isEmpty()) {
795 LOG.info("Adding VPN Interface for port {}", portupdate.getUuid().getValue());
796 nvpnManager.createVpnInterface(newVpnIds, portupdate, confTx);
797 if (!newRouterIds.isEmpty()) {
798 for (Uuid routerId : newRouterIds) {
799 nvpnManager.addToNeutronRouterInterfacesMap(routerId,portupdate.getUuid().getValue());
806 private InterfaceAclBuilder handlePortSecurityUpdated(Port portOriginal,
807 Port portUpdated, boolean origSecurityEnabled, boolean updatedSecurityEnabled,
808 InterfaceBuilder interfaceBuilder) {
809 InterfaceAclBuilder interfaceAclBuilder = null;
810 if (origSecurityEnabled != updatedSecurityEnabled) {
811 interfaceAclBuilder = new InterfaceAclBuilder();
812 interfaceAclBuilder.setPortSecurityEnabled(updatedSecurityEnabled);
813 if (updatedSecurityEnabled) {
814 // Handle security group enabled
815 NeutronvpnUtils.populateInterfaceAclBuilder(interfaceAclBuilder, portUpdated);
816 neutronvpnUtils.populateSubnetInfo(portUpdated);
818 // Handle security group disabled
819 interfaceAclBuilder.setSecurityGroups(new ArrayList<>());
820 interfaceAclBuilder.setAllowedAddressPairs(new ArrayList<>());
823 if (updatedSecurityEnabled) {
824 // handle SG add/delete delta
825 InterfaceAcl interfaceAcl = interfaceBuilder.augmentation(InterfaceAcl.class);
826 interfaceAclBuilder = new InterfaceAclBuilder(interfaceAcl);
827 interfaceAclBuilder.setSecurityGroups(
828 NeutronvpnUtils.getUpdatedSecurityGroups(interfaceAcl.getSecurityGroups(),
829 portOriginal.getSecurityGroups(), portUpdated.getSecurityGroups()));
830 List<AllowedAddressPairs> updatedAddressPairs = NeutronvpnUtils.getUpdatedAllowedAddressPairs(
831 interfaceAcl.getAllowedAddressPairs(), portOriginal.getAllowedAddressPairs(),
832 portUpdated.getAllowedAddressPairs());
833 interfaceAclBuilder.setAllowedAddressPairs(NeutronvpnUtils.getAllowedAddressPairsForFixedIps(
834 updatedAddressPairs, portOriginal.getMacAddress(), portOriginal.getFixedIps(),
835 portUpdated.getFixedIps()));
837 if (portOriginal.getFixedIps() != null
838 && !portOriginal.getFixedIps().equals(portUpdated.getFixedIps())) {
839 neutronvpnUtils.populateSubnetInfo(portUpdated);
843 return interfaceAclBuilder;
846 private String createOfPortInterface(Port port, TypedWriteTransaction<Datastore.Configuration> wrtConfigTxn) {
847 Interface inf = createInterface(port);
848 String infName = inf.getName();
850 InstanceIdentifier<Interface> interfaceIdentifier = NeutronvpnUtils.buildVlanInterfaceIdentifier(infName);
852 Optional<Interface> optionalInf =
853 SingleTransactionDataBroker.syncReadOptional(dataBroker, LogicalDatastoreType.CONFIGURATION,
854 interfaceIdentifier);
855 if (!optionalInf.isPresent()) {
856 wrtConfigTxn.put(interfaceIdentifier, inf);
858 LOG.warn("Interface {} is already present", infName);
860 } catch (ReadFailedException e) {
861 LOG.error("failed to create interface {}", infName, e);
866 private Interface createInterface(Port port) {
867 String interfaceName = port.getUuid().getValue();
868 IfL2vlan.L2vlanMode l2VlanMode = IfL2vlan.L2vlanMode.Trunk;
869 InterfaceBuilder interfaceBuilder = new InterfaceBuilder();
870 IfL2vlanBuilder ifL2vlanBuilder = new IfL2vlanBuilder();
872 Network network = neutronvpnUtils.getNeutronNetwork(port.getNetworkId());
873 Boolean isVlanTransparent = network.isVlanTransparent();
874 if (isVlanTransparent != null && isVlanTransparent) {
875 l2VlanMode = IfL2vlan.L2vlanMode.Transparent;
878 ifL2vlanBuilder.setL2vlanMode(l2VlanMode);
880 interfaceBuilder.setEnabled(true).setName(interfaceName).setType(L2vlan.class)
881 .addAugmentation(IfL2vlan.class, ifL2vlanBuilder.build());
883 if (NeutronvpnUtils.getPortSecurityEnabled(port)) {
884 InterfaceAclBuilder interfaceAclBuilder = new InterfaceAclBuilder();
885 interfaceAclBuilder.setPortSecurityEnabled(true);
886 NeutronvpnUtils.populateInterfaceAclBuilder(interfaceAclBuilder, port);
887 interfaceBuilder.addAugmentation(InterfaceAcl.class, interfaceAclBuilder.build());
888 neutronvpnUtils.populateSubnetInfo(port);
890 return interfaceBuilder.build();
893 private void deleteOfPortInterface(Port port, TypedWriteTransaction<Datastore.Configuration> wrtConfigTxn) {
894 String name = port.getUuid().getValue();
895 LOG.debug("Removing OFPort Interface {}", name);
896 InstanceIdentifier<Interface> interfaceIdentifier = NeutronvpnUtils.buildVlanInterfaceIdentifier(name);
898 Optional<Interface> optionalInf =
899 SingleTransactionDataBroker.syncReadOptional(dataBroker, LogicalDatastoreType.CONFIGURATION,
900 interfaceIdentifier);
901 if (optionalInf.isPresent()) {
902 wrtConfigTxn.delete(interfaceIdentifier);
904 LOG.warn("deleteOfPortInterface: Interface {} is not present", name);
906 } catch (ReadFailedException e) {
907 LOG.error("deleteOfPortInterface: Failed to delete interface {}", name, e);
911 private void createElanInterface(Port port, String name,
912 TypedWriteTransaction<Datastore.Configuration> wrtConfigTxn) {
913 String elanInstanceName = port.getNetworkId().getValue();
914 List<StaticMacEntries> staticMacEntries = NeutronvpnUtils.buildStaticMacEntry(port);
916 InstanceIdentifier<ElanInterface> id = InstanceIdentifier.builder(ElanInterfaces.class).child(ElanInterface
917 .class, new ElanInterfaceKey(name)).build();
918 ElanInterface elanInterface = new ElanInterfaceBuilder().setElanInstanceName(elanInstanceName)
919 .setName(name).setStaticMacEntries(staticMacEntries).withKey(new ElanInterfaceKey(name)).build();
920 wrtConfigTxn.put(id, elanInterface);
921 LOG.debug("Creating new ELan Interface {}", elanInterface);
924 private void deleteElanInterface(String name, TypedWriteTransaction<Datastore.Configuration> wrtConfigTxn) {
925 InstanceIdentifier<ElanInterface> id = InstanceIdentifier.builder(ElanInterfaces.class).child(ElanInterface
926 .class, new ElanInterfaceKey(name)).build();
927 wrtConfigTxn.delete(id);
930 // TODO Clean up the exception handling
931 @SuppressWarnings("checkstyle:IllegalCatch")
932 private void addToFloatingIpPortInfo(Uuid floatingIpId, Uuid floatingIpPortId, Uuid floatingIpPortSubnetId, String
933 floatingIpPortMacAddress) {
934 InstanceIdentifier id = NeutronvpnUtils.buildfloatingIpIdToPortMappingIdentifier(floatingIpId);
936 FloatingIpIdToPortMappingBuilder floatingipIdToPortMacMappingBuilder = new
937 FloatingIpIdToPortMappingBuilder().withKey(new FloatingIpIdToPortMappingKey(floatingIpId))
938 .setFloatingIpId(floatingIpId).setFloatingIpPortId(floatingIpPortId)
939 .setFloatingIpPortSubnetId(floatingIpPortSubnetId)
940 .setFloatingIpPortMacAddress(floatingIpPortMacAddress);
941 LOG.debug("Creating floating IP UUID {} to Floating IP neutron port {} mapping in Floating IP"
942 + " Port Info Config DS", floatingIpId.getValue(), floatingIpPortId.getValue());
943 MDSALUtil.syncWrite(dataBroker, LogicalDatastoreType.CONFIGURATION, id,
944 floatingipIdToPortMacMappingBuilder.build());
945 } catch (Exception e) {
946 LOG.error("Creating floating IP UUID {} to Floating IP neutron port {} mapping in Floating IP"
947 + " Port Info Config DS failed", floatingIpId.getValue(), floatingIpPortId.getValue(), e);
951 private Set<FixedIps> getFixedIpSet(List<FixedIps> fixedIps) {
952 return fixedIps != null ? new HashSet<>(fixedIps) : Collections.emptySet();