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