2 * Copyright (c) 2016 Cisco Systems, Inc. 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
9 package org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.processors;
11 import java.util.Collections;
12 import java.util.List;
14 import javax.annotation.Nonnull;
15 import javax.annotation.Nullable;
17 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
18 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
19 import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
20 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
21 import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
22 import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
23 import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
24 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
25 import org.opendaylight.groupbasedpolicy.util.SyncedChain;
26 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpPrefix;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.PhysAddress;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.UniqueId;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.Mappings;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.GbpByNeutronMappings;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.gbp.by.neutron.mappings.BaseEndpointsByPorts;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.gbp.by.neutron.mappings.base.endpoints.by.ports.BaseEndpointByPort;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.gbp.by.neutron.mappings.base.endpoints.by.ports.BaseEndpointByPortKey;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.Config;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.ExcludeFromPolicy;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.ExcludeFromPolicyBuilder;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.LoopbackCase;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.LoopbackCaseBuilder;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.TapCase;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.TapCaseBuilder;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.VhostUserCaseBuilder;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpoint;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpointBuilder;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpointKey;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.binding.rev150712.PortBindingExtension;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.binding.rev150712.binding.attributes.VifDetails;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.Routers;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.Router;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.RouterKey;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.port.attributes.FixedIps;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.Ports;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.Port;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.PortKey;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.portsecurity.rev150712.PortSecurityExtension;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.Subnets;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.Subnet;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.SubnetKey;
61 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
62 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
63 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.InstanceIdentifierBuilder;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
67 import com.google.common.annotations.VisibleForTesting;
68 import com.google.common.base.Optional;
69 import com.google.common.base.Preconditions;
70 import com.google.common.base.Strings;
72 public class PortHandler implements TransactionChainListener {
74 private static final Logger LOG = LoggerFactory.getLogger(PortHandler.class);
76 private static final String COMPUTE_OWNER = "compute";
77 private static final String DHCP_OWNER = "dhcp";
78 static final String ROUTER_OWNER = "network:router_interface";
79 private static final String[] SUPPORTED_DEVICE_OWNERS = {COMPUTE_OWNER, DHCP_OWNER, ROUTER_OWNER};
80 private static final String VHOST_USER = "vhostuser";
81 private static final String UNBOUND = "unbound";
82 private static final String VPP_INTERFACE_NAME_PREFIX = "neutron_port_";
83 private static final String TAP_PORT_NAME_PREFIX = "tap";
84 private static final String RT_PORT_NAME_PREFIX = "qr-";
85 private static final String VHOST_SOCKET_KEY = "vhostuser_socket";
86 static final String DEFAULT_NODE = "default";
88 private final NodeId routingNode;
89 private SyncedChain syncedChain;
90 private DataBroker dataBroker;
92 PortHandler(DataBroker dataBroker, NodeId routingNodeId) {
93 this.dataBroker = dataBroker;
94 this.routingNode = routingNodeId;
95 this.syncedChain = new SyncedChain(Preconditions.checkNotNull(dataBroker.createTransactionChain(this)));
98 void processCreated(Port port) {
99 Optional<BaseEndpointByPort> optBaseEpByPort =
100 syncedChain.readFromDs(LogicalDatastoreType.OPERATIONAL, createBaseEpByPortIid(port.getUuid()));
101 if (!optBaseEpByPort.isPresent()) {
104 processCreatedData(port, optBaseEpByPort.get());
107 void processCreated(BaseEndpointByPort bebp) {
108 Optional<Port> optPort =
109 syncedChain.readFromDs(LogicalDatastoreType.CONFIGURATION, createPortIid(bebp.getPortId()));
110 if (!optPort.isPresent()) {
113 processCreatedData(optPort.get(), bebp);
117 void processCreatedData(Port port, BaseEndpointByPort bebp) {
118 if (isValidVhostUser(port)
119 // this is a hack for vpp router port
120 // Openstack does not send binding details yet
121 || isValidVppRouterPort(port)) {
122 VppEndpoint vppEp = buildVppEndpoint(port, bebp);
124 LOG.warn("Cannot create vpp-endpoint from neutron port {}", port);
127 writeVppEndpoint(createVppEndpointIid(vppEp.getKey()), vppEp);
128 LOG.debug("Created vpp-endpoint {}", vppEp);
132 private boolean isValidVhostUser(Port port) {
133 PortBindingExtension portBindingExt = port.getAugmentation(PortBindingExtension.class);
134 if (portBindingExt != null) {
135 String vifType = portBindingExt.getVifType();
136 String deviceOwner = port.getDeviceOwner();
137 if (vifType != null && deviceOwner != null) {
138 if (vifType.contains(VHOST_USER)) {
139 for (String supportedDeviceOwner : SUPPORTED_DEVICE_OWNERS) {
140 if (deviceOwner.contains(supportedDeviceOwner)) {
150 void processUpdated(Port original, Port delta) {
151 if (!isUpdateNeeded(original, delta)) {
152 LOG.trace("Port update skipped, port didn`t change. before {}, after: {}", original, delta);
156 LOG.trace("Updating port before: {}, after: {}", original, delta);
157 if (isValidVhostUser(original)) {
158 Optional<BaseEndpointByPort> optBebp =
159 syncedChain.readFromDs(LogicalDatastoreType.OPERATIONAL, createBaseEpByPortIid(original.getUuid()));
160 if (!optBebp.isPresent()) {
163 LOG.trace("Updating port - deleting old port {}", optBebp.get().getPortId());
164 processDeleted(optBebp.get());
166 LOG.trace("Updating port - creating new port {}", delta.getUuid());
167 processCreated(delta);
170 private boolean isUpdateNeeded(final Port oldPort, final Port newPort) {
171 // TODO fix this to better support update of ports for VPP
172 final PortBindingExtension oldPortAugmentation = oldPort.getAugmentation(PortBindingExtension.class);
173 final PortBindingExtension newPortAugmentation = newPort.getAugmentation(PortBindingExtension.class);
175 if (newPortAugmentation == null) {
176 LOG.trace("Port {} is no longer a vhost type port, updating port...");
180 final String oldDeviceOwner = oldPort.getDeviceOwner();
181 final String oldVifType = oldPortAugmentation.getVifType();
182 final String newDeviceOwner = newPort.getDeviceOwner();
183 final String newVifType = newPortAugmentation.getVifType();
185 // TODO potential bug here
186 // Temporary change for Openstack Mitaka: If old neutron-binding:vif-type is vhost, new one
188 // device owner is ROUTER_OWNER, skip update. Openstack (or ml2) sometimes sends router
189 // update messages in
190 // incorrect order which causes unwanted port removal
191 if (oldVifType.equals(VHOST_USER) && newVifType.equals(UNBOUND) && oldDeviceOwner != null
192 && ROUTER_OWNER.equals(oldDeviceOwner) && ROUTER_OWNER.equals(newDeviceOwner)) {
194 "Port vif-type was updated from vhost to unbound. This update is currently disabled and will be skipped");
198 if (newVifType != null && !newVifType.equals(oldVifType)) {
199 LOG.trace("Vif type changed, old: {} new {}", oldVifType, newVifType);
203 final List<VifDetails> vifDetails = oldPortAugmentation.getVifDetails();
205 if (!oldPortAugmentation.getHostId().equals(newPortAugmentation.getHostId())
206 || nullToEmpty(vifDetails).size() != nullToEmpty(newPortAugmentation.getVifDetails()).size()) {
210 for (VifDetails vifDetail : nullToEmpty(vifDetails)) {
211 // check if vhostuser_socket, vhostuser_mode and port_filter are changed
212 if (!newPortAugmentation.getVifDetails().contains(vifDetail))
218 void processDeleted(BaseEndpointByPort bebp) {
219 LOG.trace("Deleting vpp-endpoint by BaseEndpointByPort {}", bebp);
220 VppEndpointKey vppEpKey = new VppEndpointKey(bebp.getAddress(), bebp.getAddressType(), bebp.getContextId(),
221 bebp.getContextType());
222 InstanceIdentifier<VppEndpoint> vppEpIid = createVppEndpointIid(vppEpKey);
223 Optional<VppEndpoint> readVppEp = syncedChain.readFromDs(LogicalDatastoreType.CONFIGURATION, vppEpIid);
224 if (readVppEp.isPresent()) {
225 writeVppEndpoint(vppEpIid, null);
226 LOG.debug("Deleted vpp-endpoint {}", vppEpKey);
230 private synchronized void writeVppEndpoint(InstanceIdentifier<VppEndpoint> vppEpIid, VppEndpoint vppEp) {
231 WriteTransaction wTx = syncedChain.newWriteOnlyTransaction();
233 wTx.put(LogicalDatastoreType.CONFIGURATION, vppEpIid, vppEp, true);
235 wTx.delete(LogicalDatastoreType.CONFIGURATION, vppEpIid);
237 syncedChain.submitNow(wTx);
241 VppEndpoint buildVppEndpoint(Port port, BaseEndpointByPort bebp) {
242 PortBindingExtension portBinding = port.getAugmentation(PortBindingExtension.class);
243 ExcludeFromPolicy excludeFromPolicy = new ExcludeFromPolicyBuilder().setExcludeFromPolicy(true).build();
244 VppEndpointBuilder vppEpBuilder = new VppEndpointBuilder().setDescription("neutron port")
245 .setContextId(bebp.getContextId())
246 .setContextType(bebp.getContextType())
247 .setAddress(bebp.getAddress())
248 .setAddressType(bebp.getAddressType())
249 .setVppInterfaceName(VPP_INTERFACE_NAME_PREFIX + bebp.getPortId().getValue())
250 .setVppNodeId(new NodeId(portBinding.getHostId()));
252 if (port.getDeviceOwner().contains(COMPUTE_OWNER)) {
253 vppEpBuilder.setInterfaceTypeChoice(
254 new VhostUserCaseBuilder().setSocket(getSocketFromPortBinding(portBinding)).build());
255 Optional<PortSecurityExtension> portSecurity =
256 Optional.fromNullable(port.getAugmentation(PortSecurityExtension.class));
257 if (portSecurity.isPresent() && !portSecurity.get().isPortSecurityEnabled()) {
258 vppEpBuilder.addAugmentation(ExcludeFromPolicy.class, excludeFromPolicy);
261 } else if (port.getDeviceOwner().contains(DHCP_OWNER) && port.getMacAddress() != null) {
262 IpAddress dhcpServerIpAddress = port.getFixedIps().stream().findFirst().isPresent() ?
263 port.getFixedIps().stream().findFirst().get().getIpAddress() : null;
264 TapCase tapCase = new TapCaseBuilder().setPhysicalAddress(new PhysAddress(port.getMacAddress().getValue()))
265 .setName(createPortName(port.getUuid()))
266 .setDhcpServerAddress(dhcpServerIpAddress)
268 vppEpBuilder.setInterfaceTypeChoice(tapCase);
270 } else if (isValidQRouterPort(port)) {
271 TapCase tapCase = new TapCaseBuilder().setPhysicalAddress(new PhysAddress(port.getMacAddress().getValue()))
272 .setName(createQRouterPortName(port.getUuid()))
274 vppEpBuilder.setInterfaceTypeChoice(tapCase);
275 vppEpBuilder.addAugmentation(ExcludeFromPolicy.class, excludeFromPolicy);
277 } else if (isValidVppRouterPort(port)) {
278 if (!DEFAULT_NODE.equals(routingNode.getValue())) {
280 "Host-id changed by ODL for port {}. This is a supplementary workaround for choosing a routing node.",
282 vppEpBuilder.setVppNodeId(routingNode);
283 } else if (port.getDeviceId() != null) {
284 LOG.debug("Resolving host-id for unbound router port {}", port.getUuid());
285 Optional<Ports> optPorts = syncedChain.readFromDs(LogicalDatastoreType.CONFIGURATION,
286 InstanceIdentifier.builder(Neutron.class).child(Ports.class).build());
287 if (optPorts.isPresent() && optPorts.get().getPort() != null) {
288 java.util.Optional<Port> optPortOnTheSameNode = optPorts.get()
291 .filter(p -> !p.getUuid().equals(port.getUuid()))
292 .filter(p -> p.getAugmentation(PortBindingExtension.class) != null)
293 .filter(p -> p.getDeviceOwner().contains(DHCP_OWNER))
295 if (optPortOnTheSameNode.isPresent()) {
296 PortBindingExtension binding =
297 optPortOnTheSameNode.get().getAugmentation(PortBindingExtension.class);
298 if (binding != null && binding.getHostId() != null) {
299 vppEpBuilder.setVppNodeId(new NodeId(binding.getHostId()));
301 LOG.warn("Cannot resolve location of router-port {}", port.getUuid());
307 vppEpBuilder.addAugmentation(ExcludeFromPolicy.class, excludeFromPolicy);
308 vppEpBuilder.setInterfaceTypeChoice(getLoopbackCase(port));
310 return vppEpBuilder.build();
313 private String getSocketFromPortBinding(@Nonnull PortBindingExtension portBindingExtension) {
314 List<VifDetails> vifDetails = nullToEmpty(portBindingExtension.getVifDetails());
316 for (VifDetails detail : vifDetails) {
317 if (VHOST_SOCKET_KEY.equalsIgnoreCase(detail.getDetailsKey())) {
318 return detail.getValue();
324 private LoopbackCase getLoopbackCase(Port port) {
325 LoopbackCaseBuilder loopbackCase =
326 new LoopbackCaseBuilder().setPhysAddress(new PhysAddress(port.getMacAddress().getValue()));
327 Optional<FixedIps> fixedIpsOptional = resolveFirstFixedIps(port);
328 if (fixedIpsOptional.isPresent() && fixedIpsOptional.get().getIpAddress() != null) {
329 loopbackCase.setIpAddress(fixedIpsOptional.get().getIpAddress());
330 Optional<Subnet> subnetOptional = syncedChain.readFromDs(LogicalDatastoreType.CONFIGURATION,
331 InstanceIdentifier.builder(Neutron.class)
332 .child(Subnets.class)
333 .child(Subnet.class, new SubnetKey(fixedIpsOptional.get().getSubnetId()))
335 if (subnetOptional.isPresent()) {
336 Ipv4Prefix ipv4Prefix = subnetOptional.get().getCidr().getIpv4Prefix();
337 loopbackCase.setIpPrefix(new IpPrefix(ipv4Prefix));
339 LOG.warn("IpPrefix for loopback port: {} was not set.", port);
341 if (loopbackCase.getIpAddress() != null && loopbackCase.getIpPrefix() != null) {
342 loopbackCase.setBvi(true);
343 LOG.trace("Creating loopback BVI interface: {} for VPP router port: {}.", loopbackCase, port);
347 LOG.warn("IpAddress for loopback port: {} was not set.", port);
349 return loopbackCase.build();
353 * If Qrouter (L3 Agent) is in use, any of Openstack neutron routers is not going be mapped
356 private boolean isValidQRouterPort(Port port) {
357 Optional<Router> optRouter = getRouterOptional(port);
358 return !optRouter.isPresent() && port.getDeviceOwner().contains(ROUTER_OWNER) && port.getMacAddress() != null;
361 private boolean isValidVppRouterPort(Port port) {
362 Optional<Router> optRouter = getRouterOptional(port);
363 return optRouter.isPresent() && port.getDeviceOwner().contains(ROUTER_OWNER) && port.getMacAddress() != null;
366 private Optional<Router> getRouterOptional(Port port) {
367 if (Strings.isNullOrEmpty(port.getDeviceId())) {
368 return Optional.absent();
370 RouterKey routerKey = null;
372 routerKey = new RouterKey(new Uuid(port.getDeviceId()));
373 } catch (IllegalArgumentException e) {
374 // port.getDeviceId() may not match Uuid.PATTERN_CONSTANTS
375 return Optional.absent();
377 InstanceIdentifier<Router> routerIid =
378 InstanceIdentifier.builder(Neutron.class).child(Routers.class).child(Router.class, routerKey).build();
379 Optional<Router> optRouter = syncedChain.readFromDs(LogicalDatastoreType.CONFIGURATION, routerIid);
383 public static Optional<FixedIps> resolveFirstFixedIps(Port port) {
384 List<FixedIps> fixedIps = port.getFixedIps();
385 if (fixedIps != null && !fixedIps.isEmpty()) {
386 return Optional.of(fixedIps.get(0));
388 return Optional.absent();
391 private String createPortName(Uuid portUuid) {
393 String uuid = portUuid.getValue();
394 if (uuid != null && uuid.length() >= 12) {
395 tapPortName = TAP_PORT_NAME_PREFIX + uuid.substring(0, 11);
397 tapPortName = TAP_PORT_NAME_PREFIX + uuid;
402 private String createQRouterPortName(Uuid portUuid) {
404 String uuid = portUuid.getValue();
405 if (uuid != null && uuid.length() >= 12) {
406 tapPortName = RT_PORT_NAME_PREFIX + uuid.substring(0, 11);
408 tapPortName = RT_PORT_NAME_PREFIX + uuid;
413 private InstanceIdentifier<VppEndpoint> createVppEndpointIid(VppEndpointKey vppEpKey) {
414 return InstanceIdentifier.builder(Config.class).child(VppEndpoint.class, vppEpKey).build();
417 private InstanceIdentifier<BaseEndpointByPort> createBaseEpByPortIid(Uuid uuid) {
418 return createBaseEpByPortIid(new UniqueId(uuid.getValue()));
421 private InstanceIdentifier<BaseEndpointByPort> createBaseEpByPortIid(UniqueId uuid) {
422 return InstanceIdentifier.builder(Mappings.class)
423 .child(GbpByNeutronMappings.class)
424 .child(BaseEndpointsByPorts.class)
425 .child(BaseEndpointByPort.class, new BaseEndpointByPortKey(uuid))
429 InstanceIdentifier<Port> createWildcartedPortIid() {
430 return portsIid().child(Port.class).build();
433 private InstanceIdentifier<Port> createPortIid(UniqueId uuid) {
434 return portsIid().child(Port.class, new PortKey(new Uuid(uuid.getValue()))).build();
437 private InstanceIdentifierBuilder<Ports> portsIid() {
438 return InstanceIdentifier.builder(Neutron.class).child(Ports.class);
442 public void onTransactionChainFailed(TransactionChain<?, ?> chain, AsyncTransaction<?, ?> transaction,
444 LOG.error("Transaction chain failed. {} \nTransaction which caused the chain to fail {}", cause.getMessage(),
446 syncedChain.closeChain();
447 this.syncedChain = new SyncedChain(Preconditions.checkNotNull(dataBroker.createTransactionChain(this)));
451 public void onTransactionChainSuccessful(TransactionChain<?, ?> chain) {
452 LOG.trace("Transaction chain was successful. {}", chain);
455 private <T> List<T> nullToEmpty(@Nullable List<T> list) {
456 return list == null ? Collections.emptyList() : list;