baae7f80e25985c560a2533961d02ffd5bc117e2
[onosfw.git] /
1 /*
2  * Copyright 2015 Open Networking Laboratory
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.onosproject.openstackswitching;
17
18 import com.google.common.collect.Lists;
19 import com.google.common.collect.Maps;
20 import org.apache.felix.scr.annotations.Activate;
21 import org.apache.felix.scr.annotations.Component;
22 import org.apache.felix.scr.annotations.Deactivate;
23 import org.apache.felix.scr.annotations.Reference;
24 import org.apache.felix.scr.annotations.ReferenceCardinality;
25 import org.apache.felix.scr.annotations.Service;
26 import org.onlab.packet.Ethernet;
27 import org.onlab.packet.IPv4;
28 import org.onlab.packet.Ip4Address;
29 import org.onlab.packet.Ip4Prefix;
30 import org.onlab.packet.MacAddress;
31 import org.onlab.packet.UDP;
32 import org.onosproject.core.ApplicationId;
33 import org.onosproject.core.CoreService;
34 import org.onosproject.net.Device;
35 import org.onosproject.net.DeviceId;
36 import org.onosproject.net.Port;
37 import org.onosproject.net.device.DeviceEvent;
38 import org.onosproject.net.device.DeviceListener;
39 import org.onosproject.net.device.DeviceService;
40 import org.onosproject.net.flowobjective.FlowObjectiveService;
41 import org.onosproject.net.packet.InboundPacket;
42 import org.onosproject.net.packet.PacketContext;
43 import org.onosproject.net.packet.PacketProcessor;
44 import org.onosproject.net.packet.PacketService;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import java.util.HashMap;
49 import java.util.List;
50 import java.util.concurrent.ExecutorService;
51 import java.util.concurrent.Executors;
52
53 @SuppressWarnings("ALL")
54 @Service
55 @Component(immediate = true)
56 /**
57  * It populates forwarding rules for VMs created by Openstack.
58  */
59 public class OpenstackSwitchingManager implements OpenstackSwitchingService {
60
61     private static Logger log = LoggerFactory
62             .getLogger(OpenstackSwitchingManager.class);
63
64     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
65     protected CoreService coreService;
66
67     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
68     protected PacketService packetService;
69
70     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
71     protected DeviceService deviceService;
72
73     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
74     protected FlowObjectiveService flowObjectiveService;
75
76
77     public static final int DHCP_PORT = 67;
78
79     private ApplicationId appId;
80     private OpenstackArpHandler arpHandler;
81     private OpenstackDhcpHandler dhcpHandler = new OpenstackDhcpHandler();
82     private OpenstackSwitchingRulePopulator rulePopulator;
83     private ExecutorService deviceEventExcutorService = Executors.newFixedThreadPool(10);
84
85     private InternalPacketProcessor internalPacketProcessor = new InternalPacketProcessor();
86     private InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
87
88     // Map <port_id, OpenstackPort>
89     private HashMap<String, OpenstackPort> openstackPortMap;
90     // Map <network_id, OpenstackNetwork>
91     private HashMap<String, OpenstackNetwork> openstackNetworkMap;
92     // Map <vni, List <Entry <portName, host ip>>
93     private HashMap<String, List<PortInfo>> vniPortMap;
94     private HashMap<Ip4Address, Port> tunnelPortMap;
95
96
97     @Activate
98     protected void activate() {
99         appId = coreService
100                 .registerApplication("org.onosproject.openstackswitching");
101         rulePopulator = new OpenstackSwitchingRulePopulator(appId, flowObjectiveService);
102         packetService.addProcessor(internalPacketProcessor, PacketProcessor.director(1));
103         deviceService.addListener(internalDeviceListener);
104
105         openstackPortMap = Maps.newHashMap();
106         openstackNetworkMap = Maps.newHashMap();
107         vniPortMap = Maps.newHashMap();
108         tunnelPortMap = Maps.newHashMap();
109
110         arpHandler = new OpenstackArpHandler(openstackPortMap);
111
112         log.info("Started");
113     }
114
115     @Deactivate
116     protected void deactivate() {
117         packetService.removeProcessor(internalPacketProcessor);
118         deviceService.removeListener(internalDeviceListener);
119
120         deviceEventExcutorService.shutdown();
121
122         log.info("Stopped");
123     }
124
125     @Override
126     public void createPorts(OpenstackPort openstackPort) {
127         openstackPortMap.put(openstackPort.id(), openstackPort);
128     }
129
130     @Override
131     public void deletePorts() {
132
133     }
134
135     @Override
136     public void updatePorts() {
137
138     }
139
140     @Override
141     public void createNetwork(OpenstackNetwork openstackNetwork) {
142         openstackNetworkMap.put(openstackNetwork.id(), openstackNetwork);
143     }
144
145     private void processDeviceAdded(Device device) {
146         log.warn("device {} is added", device.id());
147         rulePopulator.populateDefaultRules(device.id());
148     }
149
150     private void processPortAdded(Device device, Port port) {
151         // TODO: Simplify the data structure to store the network info
152         // TODO: Make it stateless
153         // TODO: All the logics need to be processed inside of the rulePopulator class
154         synchronized (vniPortMap) {
155             log.warn("port {} is updated", port.toString());
156
157             updatePortMaps(device, port);
158             if (!port.annotations().value("portName").equals("vxlan")) {
159                 populateFlowRulesForTrafficToSameCnode(device, port);
160                 populateFlowRulesForTrafficToDifferentCnode(device, port);
161             }
162         }
163     }
164
165     private void processPortRemoved(Device device, Port port) {
166         log.warn("port {} is removed", port.toString());
167         // TODO: need to update the vniPortMap
168     }
169
170     /**
171      * Populates the flow rules for traffic to VMs in different Cnode using
172      * Nicira extention.
173      *
174      * @param device device to put rules
175      * @param port port information of the VM
176      */
177     private void populateFlowRulesForTrafficToDifferentCnode(Device device, Port port) {
178         String portName = port.annotations().value("portName");
179         String channelId = device.annotations().value("channelId");
180         Ip4Address hostIpAddress = Ip4Address.valueOf(channelId.split(":")[0]);
181         Ip4Address fixedIp = getFixedIpAddressForPort(portName);
182         // TODO: Avoid duplicate flow rule set up for VMs in other Cnode
183         //       (possibly avoided by flowrule subsystem?)
184         if (tunnelPortMap.get(hostIpAddress) == null) {
185             log.warn("There is no tunnel port information");
186             return;
187         }
188         String vni = getVniForPort(portName);
189         MacAddress vmMac = getVmMacAddressForPort(portName);
190         if (!vniPortMap.isEmpty() && vniPortMap.get(vni) != null) {
191             for (PortInfo portInfo : vniPortMap.get(vni)) {
192                 if (!portInfo.portName.equals(portName) &&
193                         !portInfo.hostIp.equals(hostIpAddress)) {
194                     MacAddress vmMacx = getVmMacAddressForPort(portInfo.portName);
195                     rulePopulator.populateForwardingRuleForOtherCnode(vni,
196                             device.id(), portInfo.hostIp, portInfo.fixedIp, vmMacx,
197                             tunnelPortMap.get(hostIpAddress).number(),
198                             portInfo.deviceId, hostIpAddress, fixedIp, vmMac,
199                             tunnelPortMap.get(portInfo.hostIp).number());
200                 }
201             }
202         }
203     }
204
205     /**
206      * Populates the flow rules for traffic to VMs in the same Cnode as the sender.
207      *
208      * @param device device to put the rules
209      * @param port port info of the VM
210      */
211     private void populateFlowRulesForTrafficToSameCnode(Device device, Port port) {
212         Ip4Prefix cidr = getCidrForPort(port.annotations().value("portName"));
213         Ip4Address vmIp = getFixedIpAddressForPort(port.annotations().value("portName"));
214         if (vmIp != null) {
215             rulePopulator.populateForwardingRule(vmIp, device.id(), port, cidr);
216         }
217     }
218
219     /**
220      * Updates the port maps using the port information.
221      *
222      * @param device device info
223      * @param port port of the VM
224      */
225     private void updatePortMaps(Device device, Port port) {
226         String portName = port.annotations().value("portName");
227         String channelId = device.annotations().value("channelId");
228         Ip4Address hostIpAddress = Ip4Address.valueOf(channelId.split(":")[0]);
229         if (portName.startsWith("vxlan")) {
230             tunnelPortMap.put(hostIpAddress, port);
231         } else {
232             String vni = getVniForPort(portName);
233             Ip4Address fixedIp = getFixedIpAddressForPort(portName);
234             if (vniPortMap.get(vni) == null) {
235                 vniPortMap.put(vni, Lists.newArrayList());
236             }
237             vniPortMap.get(vni).add(new PortInfo(device.id(), portName, fixedIp, hostIpAddress));
238         }
239     }
240
241     /**
242      * Returns CIDR information from the subnet map for the port.
243      *
244      * @param portName port name of the port of the VM
245      * @return CIDR of the VNI of the VM
246      */
247     private Ip4Prefix getCidrForPort(String portName) {
248         String networkId = null;
249         String uuid = portName.substring(3);
250         OpenstackPort port = openstackPortMap.values().stream()
251                 .filter(p -> p.id().startsWith(uuid))
252                 .findFirst().get();
253         if (port == null) {
254             log.warn("No port information for port {}", portName);
255             return null;
256         }
257
258         //OpenstackSubnet subnet = openstackSubnetMap.values().stream()
259         //        .filter(s -> s.networkId().equals(port.networkId()))
260         //        .findFirst().get();
261         //if (subnet == null) {
262         //    log.warn("No subnet information for network {}", subnet.id());
263         //    return null;
264         //}
265
266         //return Ip4Prefix.valueOf(subnet.cidr());
267         return null;
268     }
269
270     /**
271      * Returns the VNI of the VM of the port.
272      *
273      * @param portName VM port
274      * @return VNI
275      */
276     private String getVniForPort(String portName) {
277         String networkId = null;
278         String uuid = portName.substring(3);
279         OpenstackPort port = openstackPortMap.values().stream()
280                 .filter(p -> p.id().startsWith(uuid))
281                 .findFirst().get();
282         if (port == null) {
283             log.warn("No port information for port {}", portName);
284             return null;
285         }
286         OpenstackNetwork network = openstackNetworkMap.values().stream()
287                 .filter(n -> n.id().equals(port.networkId()))
288                 .findFirst().get();
289         if (network == null) {
290             log.warn("No VNI information for network {}", network.id());
291             return null;
292         }
293
294         return network.segmentId();
295     }
296
297     /**
298      * Returns the Fixed IP address of the VM.
299      *
300      * @param portName VM port info
301      * @return IP address of the VM
302      */
303     private Ip4Address getFixedIpAddressForPort(String portName) {
304
305         // FIXME - For now we use the information stored from neutron Rest API call.
306         // TODO - Later, the information needs to be extracted from Neutron on-demand.
307         String uuid = portName.substring(3);
308         OpenstackPort port = openstackPortMap.values().stream()
309                         .filter(p -> p.id().startsWith(uuid))
310                         .findFirst().get();
311
312         if (port == null) {
313             log.error("There is no port information for port name {}", portName);
314             return null;
315         }
316
317         if (port.fixedIps().isEmpty()) {
318             log.error("There is no fixed IP info in the port information");
319             return null;
320         }
321
322         return (Ip4Address) port.fixedIps().values().toArray()[0];
323     }
324
325     /**
326      * Returns the MAC address of the VM of the port.
327      *
328      * @param portName VM port
329      * @return MAC address of the VM
330      */
331     private MacAddress getVmMacAddressForPort(String portName) {
332
333         String uuid = portName.substring(3);
334         OpenstackPort port = openstackPortMap.values().stream()
335                 .filter(p -> p.id().startsWith(uuid))
336                 .findFirst().get();
337
338         if (port == null) {
339             log.error("There is no mac information for port name {}", portName);
340             return null;
341         }
342
343         return port.macAddress();
344     }
345
346     private class InternalPacketProcessor implements PacketProcessor {
347
348         @Override
349         public void process(PacketContext context) {
350
351             if (context.isHandled()) {
352                 return;
353             }
354
355             InboundPacket pkt = context.inPacket();
356             Ethernet ethernet = pkt.parsed();
357
358             if (ethernet.getEtherType() == Ethernet.TYPE_ARP) {
359                 arpHandler.processPacketIn(pkt);
360             } else if (ethernet.getEtherType() == Ethernet.TYPE_IPV4) {
361                 IPv4 ipPacket = (IPv4) ethernet.getPayload();
362
363                 if (ipPacket.getProtocol() == IPv4.PROTOCOL_UDP) {
364                     UDP udpPacket = (UDP) ipPacket.getPayload();
365                     if (udpPacket.getDestinationPort() == DHCP_PORT) {
366                         dhcpHandler.processPacketIn(pkt);
367                     }
368                 }
369             }
370         }
371     }
372
373     private class InternalDeviceListener implements DeviceListener {
374
375         @Override
376         public void event(DeviceEvent event) {
377             deviceEventExcutorService.execute(new InternalEventHandler(event));
378         }
379     }
380
381     private class InternalEventHandler implements Runnable {
382
383         volatile DeviceEvent deviceEvent;
384
385         InternalEventHandler(DeviceEvent deviceEvent) {
386             this.deviceEvent = deviceEvent;
387         }
388
389         @Override
390         public void run() {
391             switch (deviceEvent.type()) {
392                 case DEVICE_ADDED:
393                     processDeviceAdded((Device) deviceEvent.subject());
394                     break;
395                 case DEVICE_UPDATED:
396                     Port port = (Port) deviceEvent.subject();
397                     if (port.isEnabled()) {
398                         processPortAdded((Device) deviceEvent.subject(), deviceEvent.port());
399                     }
400                     break;
401                 case DEVICE_AVAILABILITY_CHANGED:
402                     Device device = (Device) deviceEvent.subject();
403                     if (deviceService.isAvailable(device.id())) {
404                         processDeviceAdded(device);
405                     }
406                     break;
407                 case PORT_ADDED:
408                     processPortAdded((Device) deviceEvent.subject(), deviceEvent.port());
409                     break;
410                 case PORT_UPDATED:
411                     processPortAdded((Device) deviceEvent.subject(), deviceEvent.port());
412                     break;
413                 case PORT_REMOVED:
414                     processPortRemoved((Device) deviceEvent.subject(), deviceEvent.port());
415                     break;
416                 default:
417                     break;
418             }
419         }
420     }
421
422     private final class PortInfo {
423         DeviceId deviceId;
424         String portName;
425         Ip4Address fixedIp;
426         Ip4Address hostIp;
427
428         private PortInfo(DeviceId deviceId, String portName, Ip4Address fixedIp,
429                          Ip4Address hostIp) {
430             this.deviceId = deviceId;
431             this.portName = portName;
432             this.fixedIp = fixedIp;
433             this.hostIp = hostIp;
434         }
435     }
436
437 }