3c25a8c913e0b9b56c8bd3a157bc08105b0b2501
[groupbasedpolicy.git] / neutron-vpp-mapper / src / main / java / org / opendaylight / groupbasedpolicy / neutron / vpp / mapper / processors / PortHandler.java
1 /*\r
2  * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.\r
3  *\r
4  * This program and the accompanying materials are made available under the\r
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
6  * and is available at http://www.eclipse.org/legal/epl-v10.html\r
7  */\r
8 \r
9 package org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.processors;\r
10 \r
11 import java.util.Collections;\r
12 import java.util.List;\r
13 \r
14 import javax.annotation.Nullable;\r
15 \r
16 import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;\r
17 import org.opendaylight.controller.md.sal.binding.api.DataBroker;\r
18 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;\r
19 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;\r
20 import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;\r
21 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;\r
22 import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;\r
23 import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;\r
24 import org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.SocketInfo;\r
25 import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;\r
26 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpPrefix;\r
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix;\r
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.PhysAddress;\r
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;\r
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.UniqueId;\r
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.Mappings;\r
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.GbpByNeutronMappings;\r
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.gbp.by.neutron.mappings.BaseEndpointsByPorts;\r
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.gbp.by.neutron.mappings.base.endpoints.by.ports.BaseEndpointByPort;\r
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.gbp.by.neutron.mappings.base.endpoints.by.ports.BaseEndpointByPortKey;\r
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.Config;\r
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.LoopbackCase;\r
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.LoopbackCaseBuilder;\r
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.TapCase;\r
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.TapCaseBuilder;\r
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.VhostUserCaseBuilder;\r
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpoint;\r
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpointBuilder;\r
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpointKey;\r
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.binding.rev150712.PortBindingExtension;\r
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.binding.rev150712.binding.attributes.VifDetails;\r
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.Routers;\r
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.Router;\r
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.RouterKey;\r
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.port.attributes.FixedIps;\r
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.Ports;\r
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.Port;\r
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.PortKey;\r
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;\r
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.Subnets;\r
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.Subnet;\r
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.SubnetKey;\r
58 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;\r
59 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;\r
60 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.InstanceIdentifierBuilder;\r
61 import org.slf4j.Logger;\r
62 import org.slf4j.LoggerFactory;\r
63 \r
64 import com.google.common.annotations.VisibleForTesting;\r
65 import com.google.common.base.Optional;\r
66 \r
67 public class PortHandler implements TransactionChainListener {\r
68 \r
69     private static final Logger LOG = LoggerFactory.getLogger(MappingProvider.class);\r
70 \r
71     private static final String COMPUTE_OWNER = "compute";\r
72     private static final String DHCP_OWNER = "dhcp";\r
73     private static final String ROUTER_OWNER = "network:router_interface";\r
74     private static final String[] SUPPORTED_DEVICE_OWNERS = {COMPUTE_OWNER, DHCP_OWNER, ROUTER_OWNER};\r
75     private static final String VHOST_USER = "vhostuser";\r
76     private static final String VPP_INTERFACE_NAME_PREFIX = "neutron_port_";\r
77     private static final String TAP_PORT_NAME_PREFIX = "tap";\r
78     private static final String RT_PORT_NAME_PREFIX = "qr-";\r
79 \r
80     private BindingTransactionChain transactionChain;\r
81     private DataBroker dataBroker;\r
82     private SocketInfo socketInfo;\r
83 \r
84     PortHandler(DataBroker dataBroker, SocketInfo socketInfo) {\r
85         this.dataBroker = dataBroker;\r
86         this.socketInfo = socketInfo;\r
87         transactionChain = this.dataBroker.createTransactionChain(this);\r
88     }\r
89 \r
90     void processCreated(Port port) {\r
91         ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction();\r
92         Optional<BaseEndpointByPort> optBaseEpByPort = DataStoreHelper.readFromDs(LogicalDatastoreType.OPERATIONAL,\r
93                 createBaseEpByPortIid(port.getUuid()), rTx);\r
94         rTx.close();\r
95         if (!optBaseEpByPort.isPresent()) {\r
96             return;\r
97         }\r
98         processCreatedData(port, optBaseEpByPort.get());\r
99     }\r
100 \r
101     void processCreated(BaseEndpointByPort bebp) {\r
102         ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction();\r
103         Optional<Port> optPort = DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION,\r
104                 createPortIid(bebp.getPortId()), rTx);\r
105         rTx.close();\r
106         if (!optPort.isPresent()) {\r
107             return;\r
108         }\r
109         processCreatedData(optPort.get(), bebp);\r
110     }\r
111 \r
112     @VisibleForTesting\r
113     void processCreatedData(Port port, BaseEndpointByPort bebp) {\r
114         if (isValidVhostUser(port)) {\r
115             VppEndpoint vppEp = buildVppEndpoint(port, bebp);\r
116             writeVppEndpoint(createVppEndpointIid(vppEp.getKey()), vppEp);\r
117             LOG.debug("Created vpp-endpoint {}", vppEp);\r
118         }\r
119     }\r
120 \r
121     private boolean isValidVhostUser(Port port) {\r
122         PortBindingExtension portBindingExt = port.getAugmentation(PortBindingExtension.class);\r
123         if (portBindingExt != null) {\r
124             String vifType = portBindingExt.getVifType();\r
125             String deviceOwner = port.getDeviceOwner();\r
126             if (vifType != null && deviceOwner != null) {\r
127                 if (vifType.contains(VHOST_USER)) {\r
128                     for (String supportedDeviceOwner : SUPPORTED_DEVICE_OWNERS) {\r
129                         if (deviceOwner.contains(supportedDeviceOwner)) {\r
130                             return true;\r
131                         }\r
132                     }\r
133                 }\r
134             }\r
135         }\r
136         return false;\r
137     }\r
138 \r
139     void processUpdated(Port original, Port delta) {\r
140         if (!isUpdateNeeded(original, delta)){\r
141             LOG.trace("Port update skipped, port didn`t change. before {}, after: {}" , original, delta);\r
142             return;\r
143         }\r
144 \r
145         LOG.trace("Updating port before: {}, after: {}" , original, delta);\r
146         if (isValidVhostUser(original)) {\r
147             ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction();\r
148             Optional<BaseEndpointByPort> optBebp = DataStoreHelper.readFromDs(LogicalDatastoreType.OPERATIONAL,\r
149                     createBaseEpByPortIid(original.getUuid()), rTx);\r
150             rTx.close();\r
151             if (!optBebp.isPresent()) {\r
152                 return;\r
153             }\r
154             LOG.trace("Updating port - deleting old port {}" , optBebp.get().getPortId());\r
155             processDeleted(optBebp.get());\r
156         }\r
157         LOG.trace("Updating port - creating new port {}" , delta.getUuid());\r
158         processCreated(delta);\r
159     }\r
160 \r
161     private boolean isUpdateNeeded(Port oldPort, Port newPort) {\r
162         //TODO fix this to better support update of ports for VPP\r
163         PortBindingExtension oldPortAugmentation = oldPort.getAugmentation(PortBindingExtension.class);\r
164         PortBindingExtension newPortAugmentation = newPort.getAugmentation(PortBindingExtension.class);\r
165 \r
166         List<VifDetails> vifDetails = oldPortAugmentation.getVifDetails();\r
167 \r
168         if (newPortAugmentation == null) {\r
169             LOG.trace("Port {} is no longer a vhost type port, updating port...");\r
170             return true;\r
171         }\r
172 \r
173         if (!oldPortAugmentation.getHostId().equals(newPortAugmentation.getHostId()) ||\r
174             nullToEmpty(vifDetails).size() != nullToEmpty(newPortAugmentation.getVifDetails()).size()) {\r
175             return true;\r
176         }\r
177 \r
178         for (VifDetails vifDetail : nullToEmpty(vifDetails)) {\r
179             //check if vhostuser_socket, vhostuser_mode and port_filter are changed\r
180             if (!newPortAugmentation.getVifDetails().contains(vifDetail))\r
181                 return true;\r
182         }\r
183         return false;\r
184     }\r
185 \r
186     void processDeleted(BaseEndpointByPort bebp) {\r
187         LOG.trace("Deleting vpp-endpoint by BaseEndpointByPort {}" , bebp);\r
188         VppEndpointKey vppEpKey = new VppEndpointKey(bebp.getAddress(), bebp.getAddressType(), bebp.getContextId(),\r
189                 bebp.getContextType());\r
190         InstanceIdentifier<VppEndpoint> vppEpIid = createVppEndpointIid(vppEpKey);\r
191         ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction();\r
192         Optional<VppEndpoint> readVppEp = DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION, vppEpIid, rTx);\r
193         rTx.close();\r
194         if (readVppEp.isPresent()) {\r
195             writeVppEndpoint(vppEpIid, null);\r
196             LOG.debug("Deleted vpp-endpoint {}", vppEpKey);\r
197         }\r
198     }\r
199 \r
200     private synchronized void writeVppEndpoint(InstanceIdentifier<VppEndpoint> vppEpIid, VppEndpoint vppEp) {\r
201         WriteTransaction wTx = transactionChain.newWriteOnlyTransaction();\r
202         if (vppEp != null) {\r
203             wTx.put(LogicalDatastoreType.CONFIGURATION, vppEpIid, vppEp, true);\r
204         } else {\r
205             wTx.delete(LogicalDatastoreType.CONFIGURATION, vppEpIid);\r
206         }\r
207         wTx.submit();\r
208     }\r
209 \r
210     @VisibleForTesting\r
211     VppEndpoint buildVppEndpoint(Port port, BaseEndpointByPort bebp) {\r
212         PortBindingExtension portBinding = port.getAugmentation(PortBindingExtension.class);\r
213         VppEndpointBuilder vppEpBuilder = new VppEndpointBuilder().setDescription("neutron port")\r
214             .setContextId(bebp.getContextId())\r
215             .setContextType(bebp.getContextType())\r
216             .setAddress(bebp.getAddress())\r
217             .setAddressType(bebp.getAddressType())\r
218             .setVppInterfaceName(VPP_INTERFACE_NAME_PREFIX + bebp.getPortId().getValue())\r
219             .setVppNodeId(new NodeId(portBinding.getHostId()));\r
220         if (port.getDeviceOwner().contains(COMPUTE_OWNER)) {\r
221             String socket = socketInfo.getSocketPath() + socketInfo.getSocketPrefix() + bebp.getPortId().getValue();\r
222             vppEpBuilder.setInterfaceTypeChoice(new VhostUserCaseBuilder().setSocket(socket).build());\r
223         } else if (port.getDeviceOwner().contains(DHCP_OWNER) && port.getMacAddress() != null) {\r
224             TapCase tapCase = new TapCaseBuilder().setPhysicalAddress(new PhysAddress(port.getMacAddress().getValue()))\r
225                 .setName(createPortName(port.getUuid()))\r
226                 .build();\r
227             vppEpBuilder.setInterfaceTypeChoice(tapCase);\r
228         } else if (isValidQRouterPort(port)) {\r
229             TapCase tapCase = new TapCaseBuilder().setPhysicalAddress(new PhysAddress(port.getMacAddress().getValue()))\r
230                     .setName(createQRouterPortName(port.getUuid()))\r
231                     .build();\r
232             vppEpBuilder.setInterfaceTypeChoice(tapCase);\r
233         } else if (isValidVppRouterPort(port)) {\r
234             vppEpBuilder.setInterfaceTypeChoice(getLoopbackCase(port));\r
235         }\r
236         return vppEpBuilder.build();\r
237     }\r
238 \r
239     private LoopbackCase getLoopbackCase(Port port) {\r
240         LoopbackCaseBuilder loopbackCase = new LoopbackCaseBuilder()\r
241             .setPhysAddress(new PhysAddress(port.getMacAddress().getValue()));\r
242         Optional<FixedIps> fixedIpsOptional = resolveFirstFixedIps(port);\r
243         if(fixedIpsOptional.isPresent() && fixedIpsOptional.get().getIpAddress() != null){\r
244             loopbackCase.setIpAddress(fixedIpsOptional.get().getIpAddress());\r
245             ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction();\r
246             Optional<Subnet> subnetOptional =\r
247                 DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION,\r
248                     InstanceIdentifier.builder(Neutron.class)\r
249                         .child(Subnets.class)\r
250                         .child(Subnet.class, new SubnetKey(fixedIpsOptional.get().getSubnetId()))\r
251                         .build(), rTx);\r
252             if (subnetOptional.isPresent()) {\r
253                 Ipv4Prefix ipv4Prefix = subnetOptional.get().getCidr().getIpv4Prefix();\r
254                 loopbackCase.setIpPrefix(new IpPrefix(ipv4Prefix));\r
255             } else {\r
256                 LOG.warn("IpPrefix for loopback port: {} was not set.", port);\r
257             }\r
258             if (loopbackCase.getIpAddress() != null && loopbackCase.getIpPrefix() != null) {\r
259                 loopbackCase.setBvi(true);\r
260                 LOG.trace("Creating loopback BVI interface: {} for VPP router port: {}.", loopbackCase, port);\r
261             }\r
262 \r
263         } else {\r
264             LOG.warn("IpAddress for loopback port: {} was not set.", port);\r
265         }\r
266         return loopbackCase.build();\r
267     }\r
268 \r
269     /**\r
270      * If Qrouter (L3 Agent) is in use, any of Openstack neutron routers is not going be mapped\r
271      * to ODL neutron.\r
272      */\r
273     private boolean isValidQRouterPort(Port port) {\r
274         Optional<Router> optRouter = getRouterOptional(port);\r
275         return !optRouter.isPresent() && port.getDeviceOwner().contains(ROUTER_OWNER)\r
276                 && port.getMacAddress() != null;\r
277     }\r
278 \r
279     private boolean isValidVppRouterPort(Port port) {\r
280         Optional<Router> optRouter = getRouterOptional(port);\r
281         return optRouter.isPresent() && port.getDeviceOwner().contains(ROUTER_OWNER)\r
282             && port.getMacAddress() != null;\r
283     }\r
284 \r
285     private Optional<Router> getRouterOptional(Port port) {\r
286         ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction();\r
287         InstanceIdentifier<Router> routerIid = InstanceIdentifier.builder(Neutron.class)\r
288             .child(Routers.class)\r
289             .child(Router.class, new RouterKey(new Uuid(port.getDeviceId())))\r
290             .build();\r
291         Optional<Router> optRouter = DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION, routerIid, rTx);\r
292         rTx.close();\r
293         return optRouter;\r
294     }\r
295 \r
296     public static Optional<FixedIps> resolveFirstFixedIps(Port port) {\r
297         List<FixedIps> fixedIps = port.getFixedIps();\r
298         if (fixedIps != null && !fixedIps.isEmpty()) {\r
299             return Optional.of(fixedIps.get(0));\r
300         }\r
301         return Optional.absent();\r
302     }\r
303 \r
304     private String createPortName(Uuid portUuid) {\r
305         String tapPortName;\r
306         String uuid = portUuid.getValue();\r
307         if (uuid != null && uuid.length() >= 12) {\r
308             tapPortName = TAP_PORT_NAME_PREFIX + uuid.substring(0, 11);\r
309         } else {\r
310             tapPortName = TAP_PORT_NAME_PREFIX + uuid;\r
311         }\r
312         return tapPortName;\r
313     }\r
314 \r
315     private String createQRouterPortName(Uuid portUuid) {\r
316         String tapPortName;\r
317         String uuid = portUuid.getValue();\r
318         if (uuid != null && uuid.length() >= 12) {\r
319             tapPortName = RT_PORT_NAME_PREFIX + uuid.substring(0, 11);\r
320         } else {\r
321             tapPortName = RT_PORT_NAME_PREFIX + uuid;\r
322         }\r
323         return tapPortName;\r
324     }\r
325 \r
326     private InstanceIdentifier<VppEndpoint> createVppEndpointIid(VppEndpointKey vppEpKey) {\r
327         return InstanceIdentifier.builder(Config.class).child(VppEndpoint.class, vppEpKey).build();\r
328     }\r
329 \r
330     private InstanceIdentifier<BaseEndpointByPort> createBaseEpByPortIid(Uuid uuid) {\r
331         return createBaseEpByPortIid(new UniqueId(uuid.getValue()));\r
332     }\r
333 \r
334     private InstanceIdentifier<BaseEndpointByPort> createBaseEpByPortIid(UniqueId uuid) {\r
335         return InstanceIdentifier.builder(Mappings.class)\r
336             .child(GbpByNeutronMappings.class)\r
337             .child(BaseEndpointsByPorts.class)\r
338             .child(BaseEndpointByPort.class, new BaseEndpointByPortKey(uuid))\r
339             .build();\r
340     }\r
341 \r
342     InstanceIdentifier<Port> createWildcartedPortIid() {\r
343         return portsIid().child(Port.class).build();\r
344     }\r
345 \r
346     private InstanceIdentifier<Port> createPortIid(UniqueId uuid) {\r
347         return portsIid().child(Port.class, new PortKey(new Uuid(uuid.getValue()))).build();\r
348     }\r
349 \r
350     private InstanceIdentifierBuilder<Ports> portsIid() {\r
351         return InstanceIdentifier.builder(Neutron.class).child(Ports.class);\r
352     }\r
353 \r
354     @Override\r
355     public void onTransactionChainFailed(TransactionChain<?, ?> chain, AsyncTransaction<?, ?> transaction,\r
356             Throwable cause) {\r
357         LOG.error("Transaction chain failed. {} \nTransaction which caused the chain to fail {}", cause.getMessage(),\r
358                 transaction, cause);\r
359         transactionChain.close();\r
360         transactionChain = dataBroker.createTransactionChain(this);\r
361     }\r
362 \r
363     @Override\r
364     public void onTransactionChainSuccessful(TransactionChain<?, ?> chain) {\r
365         LOG.trace("Transaction chain was successful. {}", chain);\r
366     }\r
367 \r
368     private <T> List<T> nullToEmpty(@Nullable List<T> list) {\r
369         return list == null ? Collections.emptyList() : list;\r
370     }\r
371 }\r