/* * 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 org.onlab.packet.Ethernet; import org.onlab.packet.Ip4Address; import org.onlab.packet.MacAddress; import org.onosproject.core.ApplicationId; import org.onosproject.net.Device; import org.onosproject.net.DeviceId; import org.onosproject.net.Port; import org.onosproject.net.PortNumber; import org.onosproject.net.behaviour.ExtensionTreatmentResolver; import org.onosproject.net.device.DeviceService; import org.onosproject.net.driver.DefaultDriverData; import org.onosproject.net.driver.DefaultDriverHandler; import org.onosproject.net.driver.Driver; import org.onosproject.net.driver.DriverHandler; import org.onosproject.net.driver.DriverService; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.instructions.ExtensionTreatment; import org.onosproject.net.flow.instructions.ExtensionPropertyException; import org.onosproject.net.flow.instructions.ExtensionTreatmentType; import org.onosproject.net.flowobjective.DefaultForwardingObjective; import org.onosproject.net.flowobjective.FlowObjectiveService; import org.onosproject.net.flowobjective.ForwardingObjective; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; /** * Populates switching flow rules. */ public class OpenstackSwitchingRulePopulator { private static Logger log = LoggerFactory .getLogger(OpenstackSwitchingRulePopulator.class); private static final int SWITCHING_RULE_PRIORITY = 50000; private FlowObjectiveService flowObjectiveService; private DriverService driverService; private DeviceService deviceService; private OpenstackRestHandler restHandler; private ApplicationId appId; private Collection openstackNetworkList; private Collection openstackPortList; /** * Creates OpenstackSwitchingRulPopulator. * * @param appId application id * @param flowObjectiveService FlowObjectiveService reference * @param deviceService DeviceService reference * @param driverService DriverService reference */ public OpenstackSwitchingRulePopulator(ApplicationId appId, FlowObjectiveService flowObjectiveService, DeviceService deviceService, OpenstackRestHandler restHandler, DriverService driverService) { this.flowObjectiveService = flowObjectiveService; this.deviceService = deviceService; this.driverService = driverService; this.restHandler = restHandler; this.appId = appId; openstackNetworkList = restHandler.getNetworks(); openstackPortList = restHandler.getPorts(); } /** * Populates flow rules for the VM created. * * @param device device to populate rules to * @param port port for the VM created */ public void populateSwitchingRules(Device device, Port port) { populateFlowRulesForTrafficToSameCnode(device, port); populateFlowRulesForTrafficToDifferentCnode(device, port); } /** * 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) { Ip4Address vmIp = getFixedIpAddressForPort(port.annotations().value("portName")); if (vmIp != null) { setFlowRuleForVMsInSameCnode(vmIp, device.id(), port); } } /** * 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); MacAddress vmMac = getVmMacAddressForPort(portName); String vni = getVniForPort(portName); deviceService.getAvailableDevices().forEach(d -> { if (!d.equals(device)) { deviceService.getPorts(d.id()).forEach(p -> { String pName = p.annotations().value("portName"); if (!p.equals(port) && vni.equals(getVniForPort(pName))) { String cidx = d.annotations().value("channelId"); Ip4Address hostIpx = Ip4Address.valueOf(cidx.split(":")[0]); MacAddress vmMacx = getVmMacAddressForPort(pName); Ip4Address fixedIpx = getFixedIpAddressForPort(pName); setVxLanFlowRule(vni, device.id(), hostIpx, fixedIpx, vmMacx); setVxLanFlowRule(vni, d.id(), hostIpAddress, fixedIp, vmMac); } }); } }); } /** * Returns the VNI of the VM of the port. * * @param portName VM port * @return VNI */ private String getVniForPort(String portName) { String uuid = portName.substring(3); OpenstackPort port = openstackPortList.stream() .filter(p -> p.id().startsWith(uuid)) .findAny().orElse(null); if (port == null) { log.warn("No port information for port {}", portName); return null; } OpenstackNetwork network = openstackNetworkList.stream() .filter(n -> n.id().equals(port.networkId())) .findAny().orElse(null); if (network == null) { log.warn("No VNI information for network {}", port.networkId()); 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) { String uuid = portName.substring(3); OpenstackPort port = openstackPortList.stream() .filter(p -> p.id().startsWith(uuid)) .findFirst().orElse(null); 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 = openstackPortList.stream() .filter(p -> p.id().startsWith(uuid)) .findFirst().orElse(null); if (port == null) { log.error("There is port information for port name {}", portName); return null; } return port.macAddress(); } /** * Sets the flow rules for traffic between VMs in the same Cnode. * * @param ip4Address VM IP address * @param id device ID to put rules * @param port VM port */ private void setFlowRuleForVMsInSameCnode(Ip4Address ip4Address, DeviceId id, Port port) { TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder(); TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); sBuilder.matchEthType(Ethernet.TYPE_IPV4) .matchIPDst(ip4Address.toIpPrefix()); tBuilder.setOutput(port.number()); ForwardingObjective fo = DefaultForwardingObjective.builder() .withSelector(sBuilder.build()) .withTreatment(tBuilder.build()) .withPriority(SWITCHING_RULE_PRIORITY) .withFlag(ForwardingObjective.Flag.VERSATILE) .fromApp(appId) .add(); flowObjectiveService.forward(id, fo); } /** * Sets the flow rules between traffic from VMs in different Cnode. * * @param vni VNI * @param id device ID * @param hostIp host IP of the VM * @param vmIp fixed IP of the VM * @param vmMac MAC address of the VM */ private void setVxLanFlowRule(String vni, DeviceId id, Ip4Address hostIp, Ip4Address vmIp, MacAddress vmMac) { TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder(); TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); sBuilder.matchEthType(Ethernet.TYPE_IPV4) .matchIPDst(vmIp.toIpPrefix()); tBuilder.setTunnelId(Long.parseLong(vni)) .extension(buildNiciraExtenstion(id, hostIp), id) .setOutput(getTunnelPort(id)); ForwardingObjective fo = DefaultForwardingObjective.builder() .withSelector(sBuilder.build()) .withTreatment(tBuilder.build()) .withPriority(SWITCHING_RULE_PRIORITY) .withFlag(ForwardingObjective.Flag.VERSATILE) .fromApp(appId) .add(); flowObjectiveService.forward(id, fo); } private ExtensionTreatment buildNiciraExtenstion(DeviceId id, Ip4Address hostIp) { Driver driver = driverService.getDriver(id); DriverHandler driverHandler = new DefaultDriverHandler(new DefaultDriverData(driver, id)); ExtensionTreatmentResolver resolver = driverHandler.behaviour(ExtensionTreatmentResolver.class); ExtensionTreatment extensionInstruction = resolver.getExtensionInstruction( ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST.type()); try { extensionInstruction.setPropertyValue("tunnelDst", hostIp); } catch (ExtensionPropertyException e) { log.error("Error setting Nicira extension setting {}", e); } return extensionInstruction; } private PortNumber getTunnelPort(DeviceId id) { Port port = deviceService.getPorts(id).stream() .filter(p -> p.annotations().value("portName").equals("vxlan")) .findAny().orElse(null); if (port == null) { log.error("No TunnelPort was created."); return null; } return port.number(); } }