/* * Copyright 2015 Open Networking Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onosproject.openstackswitching; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; import org.onlab.packet.Ethernet; import org.onlab.packet.Ip4Address; import org.onlab.packet.Ip4Prefix; import org.onlab.packet.MacAddress; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; import org.onosproject.dhcp.DhcpService; import org.onosproject.net.Device; import org.onosproject.net.DeviceId; import org.onosproject.net.Port; import org.onosproject.net.device.DeviceEvent; import org.onosproject.net.device.DeviceListener; import org.onosproject.net.device.DeviceService; import org.onosproject.net.flowobjective.FlowObjectiveService; import org.onosproject.net.packet.InboundPacket; import org.onosproject.net.packet.PacketContext; import org.onosproject.net.packet.PacketProcessor; import org.onosproject.net.packet.PacketService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @SuppressWarnings("ALL") @Service @Component(immediate = true) /** * It populates forwarding rules for VMs created by Openstack. */ public class OpenstackSwitchingManager implements OpenstackSwitchingService { private static Logger log = LoggerFactory .getLogger(OpenstackSwitchingManager.class); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected CoreService coreService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected PacketService packetService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected DeviceService deviceService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected FlowObjectiveService flowObjectiveService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected DhcpService dhcpService; public static final int DHCP_PORT = 67; private ApplicationId appId; private OpenstackArpHandler arpHandler; private OpenstackSwitchingRulePopulator rulePopulator; private ExecutorService deviceEventExcutorService = Executors.newFixedThreadPool(10); private InternalPacketProcessor internalPacketProcessor = new InternalPacketProcessor(); private InternalDeviceListener internalDeviceListener = new InternalDeviceListener(); // Map private Map openstackPortMap; // Map private Map openstackNetworkMap; // Map private Map openstackSubnetMap; // Map > private Map> vniPortMap; private Map tunnelPortMap; @Activate protected void activate() { appId = coreService .registerApplication("org.onosproject.openstackswitching"); rulePopulator = new OpenstackSwitchingRulePopulator(appId, flowObjectiveService); packetService.addProcessor(internalPacketProcessor, PacketProcessor.director(1)); deviceService.addListener(internalDeviceListener); openstackPortMap = Maps.newHashMap(); openstackNetworkMap = Maps.newHashMap(); openstackSubnetMap = Maps.newHashMap(); vniPortMap = Maps.newHashMap(); tunnelPortMap = Maps.newHashMap(); arpHandler = new OpenstackArpHandler(openstackPortMap, packetService); log.info("Started"); } @Deactivate protected void deactivate() { packetService.removeProcessor(internalPacketProcessor); deviceService.removeListener(internalDeviceListener); deviceEventExcutorService.shutdown(); log.info("Stopped"); } @Override public void createPorts(OpenstackPort openstackPort) { //For DHCP purpose //registerDhcpInfo(openstackPort); openstackPortMap.put(openstackPort.id(), openstackPort); } /* private void registerDhcpInfo(OpenstackPort openstackPort) { Ip4Address ip4Address; Ip4Address subnetMask; Ip4Address dhcpServer; Ip4Address gatewayIPAddress; Ip4Address domainServer; OpenstackSubnet openstackSubnet; ip4Address = (Ip4Address) openstackPort.fixedIps().values().toArray()[0]; openstackSubnet = openstackSubnetMap.values().stream() .filter(n -> n.networkId().equals(openstackPort.networkId())) .findFirst().get(); int prefix; String[] parts = openstackSubnet.cidr().split("/"); prefix = Integer.parseInt(parts[1]); int mask = 0xffffffff << (32 - prefix); byte[] bytes = new byte[]{(byte) (mask >>> 24), (byte) (mask >> 16 & 0xff), (byte) (mask >> 8 & 0xff), (byte) (mask & 0xff)}; subnetMask = Ip4Address.valueOf(bytes); gatewayIPAddress = Ip4Address.valueOf(openstackSubnet.gatewayIp()); dhcpServer = gatewayIPAddress; domainServer = Ip4Address.valueOf("8.8.8.8"); dhcpService.setStaticMappingOpenstack(openstackPort.macAddress(), ip4Address, subnetMask, dhcpServer, gatewayIPAddress, domainServer); } */ @Override public void deletePorts() { } @Override public void updatePorts() { } @Override public void createNetwork(OpenstackNetwork openstackNetwork) { openstackNetworkMap.put(openstackNetwork.id(), openstackNetwork); } @Override public void createSubnet(OpenstackSubnet openstackSubnet) { openstackSubnetMap.put(openstackSubnet.id(), openstackSubnet); log.debug("Added Subnet Info {}", openstackNetworkMap.get(openstackSubnet.id())); } private void processDeviceAdded(Device device) { log.debug("device {} is added", device.id()); rulePopulator.populateDefaultRules(device.id()); } private void processPortAdded(Device device, Port port) { // TODO: Simplify the data structure to store the network info // TODO: Make it stateless // TODO: All the logics need to be processed inside of the rulePopulator class synchronized (vniPortMap) { log.debug("port {} is updated", port.toString()); updatePortMaps(device, port); if (!port.annotations().value("portName").equals("vxlan")) { populateFlowRulesForTrafficToSameCnode(device, port); populateFlowRulesForTrafficToDifferentCnode(device, port); } } } private void processPortRemoved(Device device, Port port) { log.debug("port {} is removed", port.toString()); // TODO: need to update the vniPortMap } /** * Populates the flow rules for traffic to VMs in different Cnode using * Nicira extention. * * @param device device to put rules * @param port port information of the VM */ private void populateFlowRulesForTrafficToDifferentCnode(Device device, Port port) { String portName = port.annotations().value("portName"); String channelId = device.annotations().value("channelId"); Ip4Address hostIpAddress = Ip4Address.valueOf(channelId.split(":")[0]); Ip4Address fixedIp = getFixedIpAddressForPort(portName); // TODO: Avoid duplicate flow rule set up for VMs in other Cnode // (possibly avoided by flowrule subsystem?) if (tunnelPortMap.get(hostIpAddress) == null) { log.debug("There is no tunnel port information"); return; } String vni = getVniForPort(portName); MacAddress vmMac = getVmMacAddressForPort(portName); if (!vniPortMap.isEmpty() && vniPortMap.get(vni) != null) { for (PortInfo portInfo : vniPortMap.get(vni)) { if (!portInfo.portName.equals(portName) && !portInfo.hostIp.equals(hostIpAddress)) { MacAddress vmMacx = getVmMacAddressForPort(portInfo.portName); rulePopulator.populateForwardingRuleForOtherCnode(vni, device.id(), portInfo.hostIp, portInfo.fixedIp, vmMacx, tunnelPortMap.get(hostIpAddress).number(), portInfo.deviceId, hostIpAddress, fixedIp, vmMac, tunnelPortMap.get(portInfo.hostIp).number()); } } } } /** * Populates the flow rules for traffic to VMs in the same Cnode as the sender. * * @param device device to put the rules * @param port port info of the VM */ private void populateFlowRulesForTrafficToSameCnode(Device device, Port port) { Ip4Prefix cidr = getCidrForPort(port.annotations().value("portName")); Ip4Address vmIp = getFixedIpAddressForPort(port.annotations().value("portName")); if (vmIp != null) { rulePopulator.populateForwardingRule(vmIp, device.id(), port, cidr); } } /** * Updates the port maps using the port information. * * @param device device info * @param port port of the VM */ private void updatePortMaps(Device device, Port port) { String portName = port.annotations().value("portName"); String channelId = device.annotations().value("channelId"); Ip4Address hostIpAddress = Ip4Address.valueOf(channelId.split(":")[0]); if (portName.startsWith("vxlan")) { tunnelPortMap.put(hostIpAddress, port); } else { String vni = getVniForPort(portName); Ip4Address fixedIp = getFixedIpAddressForPort(portName); if (vniPortMap.get(vni) == null) { vniPortMap.put(vni, Lists.newArrayList()); } vniPortMap.get(vni).add(new PortInfo(device.id(), portName, fixedIp, hostIpAddress)); } } /** * Returns CIDR information from the subnet map for the port. * * @param portName port name of the port of the VM * @return CIDR of the VNI of the VM */ private Ip4Prefix getCidrForPort(String portName) { String networkId = null; String uuid = portName.substring(3); OpenstackPort port = openstackPortMap.values().stream() .filter(p -> p.id().startsWith(uuid)) .findFirst().get(); if (port == null) { log.debug("No port information for port {}", portName); return null; } OpenstackSubnet subnet = openstackSubnetMap.values().stream() .filter(s -> s.networkId().equals(port.networkId())) .findFirst().get(); if (subnet == null) { log.debug("No subnet information for network {}", subnet.id()); return null; } return Ip4Prefix.valueOf(subnet.cidr()); } /** * Returns the VNI of the VM of the port. * * @param portName VM port * @return VNI */ private String getVniForPort(String portName) { String networkId = null; String uuid = portName.substring(3); OpenstackPort port = openstackPortMap.values().stream() .filter(p -> p.id().startsWith(uuid)) .findFirst().get(); if (port == null) { log.debug("No port information for port {}", portName); return null; } OpenstackNetwork network = openstackNetworkMap.values().stream() .filter(n -> n.id().equals(port.networkId())) .findFirst().get(); if (network == null) { log.debug("No VNI information for network {}", network.id()); return null; } return network.segmentId(); } /** * Returns the Fixed IP address of the VM. * * @param portName VM port info * @return IP address of the VM */ private Ip4Address getFixedIpAddressForPort(String portName) { // FIXME - For now we use the information stored from neutron Rest API call. // TODO - Later, the information needs to be extracted from Neutron on-demand. String uuid = portName.substring(3); OpenstackPort port = openstackPortMap.values().stream() .filter(p -> p.id().startsWith(uuid)) .findFirst().get(); if (port == null) { log.error("There is no port information for port name {}", portName); return null; } if (port.fixedIps().isEmpty()) { log.error("There is no fixed IP info in the port information"); return null; } return (Ip4Address) port.fixedIps().values().toArray()[0]; } /** * Returns the MAC address of the VM of the port. * * @param portName VM port * @return MAC address of the VM */ private MacAddress getVmMacAddressForPort(String portName) { String uuid = portName.substring(3); OpenstackPort port = openstackPortMap.values().stream() .filter(p -> p.id().startsWith(uuid)) .findFirst().get(); if (port == null) { log.error("There is no mac information for port name {}", portName); return null; } return port.macAddress(); } private class InternalPacketProcessor implements PacketProcessor { @Override public void process(PacketContext context) { if (context.isHandled()) { return; } InboundPacket pkt = context.inPacket(); Ethernet ethernet = pkt.parsed(); if (ethernet.getEtherType() == Ethernet.TYPE_ARP) { arpHandler.processPacketIn(pkt); } } } private class InternalDeviceListener implements DeviceListener { @Override public void event(DeviceEvent event) { deviceEventExcutorService.execute(new InternalEventHandler(event)); } } private class InternalEventHandler implements Runnable { volatile DeviceEvent deviceEvent; InternalEventHandler(DeviceEvent deviceEvent) { this.deviceEvent = deviceEvent; } @Override public void run() { switch (deviceEvent.type()) { case DEVICE_ADDED: processDeviceAdded((Device) deviceEvent.subject()); break; case DEVICE_UPDATED: Port port = (Port) deviceEvent.subject(); if (port.isEnabled()) { processPortAdded((Device) deviceEvent.subject(), deviceEvent.port()); } break; case DEVICE_AVAILABILITY_CHANGED: Device device = (Device) deviceEvent.subject(); if (deviceService.isAvailable(device.id())) { processDeviceAdded(device); } break; case PORT_ADDED: processPortAdded((Device) deviceEvent.subject(), deviceEvent.port()); break; case PORT_UPDATED: processPortAdded((Device) deviceEvent.subject(), deviceEvent.port()); break; case PORT_REMOVED: processPortRemoved((Device) deviceEvent.subject(), deviceEvent.port()); break; default: break; } } } private final class PortInfo { DeviceId deviceId; String portName; Ip4Address fixedIp; Ip4Address hostIp; private PortInfo(DeviceId deviceId, String portName, Ip4Address fixedIp, Ip4Address hostIp) { this.deviceId = deviceId; this.portName = portName; this.fixedIp = fixedIp; this.hostIp = hostIp; } } }