blob: baae7f80e25985c560a2533961d02ffd5bc117e2 [file] [log] [blame]
sanghoshin94872a12015-10-16 18:04:34 +09001/*
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 */
16package org.onosproject.openstackswitching;
17
18import com.google.common.collect.Lists;
19import com.google.common.collect.Maps;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.apache.felix.scr.annotations.Service;
26import org.onlab.packet.Ethernet;
27import org.onlab.packet.IPv4;
28import org.onlab.packet.Ip4Address;
29import org.onlab.packet.Ip4Prefix;
30import org.onlab.packet.MacAddress;
31import org.onlab.packet.UDP;
32import org.onosproject.core.ApplicationId;
33import org.onosproject.core.CoreService;
34import org.onosproject.net.Device;
35import org.onosproject.net.DeviceId;
36import org.onosproject.net.Port;
37import org.onosproject.net.device.DeviceEvent;
38import org.onosproject.net.device.DeviceListener;
39import org.onosproject.net.device.DeviceService;
40import org.onosproject.net.flowobjective.FlowObjectiveService;
41import org.onosproject.net.packet.InboundPacket;
42import org.onosproject.net.packet.PacketContext;
43import org.onosproject.net.packet.PacketProcessor;
44import org.onosproject.net.packet.PacketService;
45import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
47
48import java.util.HashMap;
49import java.util.List;
50import java.util.concurrent.ExecutorService;
51import 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 */
59public 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}