blob: 248233325458c0f6187c0c213575a73301b87ca9 [file] [log] [blame]
/*
* Copyright 2016-present 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.openstacknetworking.impl;
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.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.util.Tools;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.core.GroupId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.PortNumber;
import org.onosproject.net.device.DeviceService;
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.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.openstackinterface.OpenstackInterfaceService;
import org.onosproject.openstackinterface.OpenstackNetwork;
import org.onosproject.openstackinterface.OpenstackRouter;
import org.onosproject.openstackinterface.OpenstackRouterInterface;
import org.onosproject.openstackinterface.OpenstackSubnet;
import org.onosproject.openstacknetworking.api.Constants;
import org.onosproject.openstacknetworking.api.OpenstackRoutingService;
import org.onosproject.openstacknode.OpenstackNode;
import org.onosproject.openstacknode.OpenstackNodeEvent;
import org.onosproject.openstacknode.OpenstackNodeListener;
import org.onosproject.openstacknode.OpenstackNodeService;
import org.onosproject.scalablegateway.api.GatewayNode;
import org.onosproject.scalablegateway.api.ScalableGatewayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.openstacknetworking.api.Constants.*;
import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.COMPUTE;
import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
@Component(immediate = true)
@Service
public class OpenstackRoutingManager extends AbstractVmHandler implements OpenstackRoutingService {
private final Logger log = LoggerFactory.getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenstackInterfaceService openstackService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenstackNodeService nodeService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ScalableGatewayService gatewayService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
private final InternalNodeListener nodeListener = new InternalNodeListener();
private ApplicationId appId;
@Activate
protected void activate() {
super.activate();
appId = coreService.registerApplication(ROUTING_APP_ID);
nodeService.addListener(nodeListener);
}
@Deactivate
protected void deactivate() {
super.deactivate();
nodeService.removeListener(nodeListener);
log.info("stopped");
}
@Override
protected void hostDetected(Host host) {
// Installs forwarding flow rules to VMs in different nodes and different subnets
// that are connected via a router
Optional<OpenstackRouter> routerOfTheHost = getRouter(host);
if (!routerOfTheHost.isPresent()) {
return;
}
routableSubNets(routerOfTheHost.get().id()).stream()
.filter(subnet -> !subnet.id().equals(host.annotations().value(SUBNET_ID)))
.forEach(subnet -> setForwardingRulesAmongHostsInDifferentCnodes(host, getHosts(subnet), true));
}
@Override
protected void hostRemoved(Host host) {
// Removes forwarding flow rules to VMs in different nodes and different subnets
// that are connected via a router
Optional<OpenstackRouter> routerOfTheHost = getRouter(host);
if (!routerOfTheHost.isPresent()) {
return;
}
routableSubNets(routerOfTheHost.get().id()).stream()
.filter(subnet -> !subnet.id().equals(host.annotations().value(SUBNET_ID)))
.forEach(subnet -> setForwardingRulesAmongHostsInDifferentCnodes(host, getHosts(subnet), false));
}
@Override
public void createRouter(OpenstackRouter osRouter) {
}
@Override
public void updateRouter(OpenstackRouter osRouter) {
if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
routableSubNets(osRouter.id()).stream()
.forEach(subnet -> setExternalConnection(osRouter, subnet, true));
log.info("Connected external gateway {} to router {}",
osRouter.gatewayExternalInfo().externalFixedIps(),
osRouter.name());
} else {
routableSubNets(osRouter.id()).stream()
.forEach(subnet -> setExternalConnection(osRouter, subnet, false));
log.info("Disconnected external gateway from router {}",
osRouter.name());
}
}
@Override
public void removeRouter(String osRouterId) {
// Nothing to do
// All interfaces need to be removed before the router is removed,
// and all related flow rues are removed when the interfaces are removed.
}
@Override
public void addRouterInterface(OpenstackRouterInterface routerIfaceAdded) {
OpenstackRouter osRouter = openstackRouter(routerIfaceAdded.id());
OpenstackSubnet osSubnetAdded = openstackService.subnet(routerIfaceAdded.subnetId());
if (osRouter == null || osSubnetAdded == null) {
log.warn("Failed to add router interface {}", routerIfaceAdded);
return;
}
handleRouterInterfaces(osRouter, osSubnetAdded);
}
@Override
public void removeRouterInterface(OpenstackRouterInterface routerIface) {
OpenstackRouter osRouter = openstackService.router(routerIface.id());
OpenstackSubnet osSubnetRemoved = openstackService.subnet(routerIface.subnetId());
if (osRouter == null) {
log.warn("Failed to remove router interface {}", routerIface);
return;
}
handleRouterInterfacesRemoved(osRouter, osSubnetRemoved);
log.info("Disconnected {} from router {}", osSubnetRemoved.cidr(), osRouter.name());
}
private void handleRouterInterfaces(OpenstackRouter osRouter, OpenstackSubnet osSubnetAdded) {
OpenstackNetwork osNetworkAdded = openstackService.network(osSubnetAdded.networkId());
if (osNetworkAdded == null) { // in case of external network subnet
return;
}
// Sets flow rules for routing among subnets connected to a router.
setRoutesAmongSubnets(osRouter, osSubnetAdded, true);
// Sets flow rules for forwarding "packets going to external networks" to gateway nodes.
if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
setExternalConnection(osRouter, osSubnetAdded, true);
}
// Sets flow rules to handle ping to the virtual gateway.
Ip4Address vGatewayIp = Ip4Address.valueOf(osSubnetAdded.gatewayIp());
gatewayService.getGatewayDeviceIds()
.forEach(deviceId -> setGatewayIcmpRule(vGatewayIp, deviceId, true));
// Sets east-west routing rules for VMs in different Cnode to Switching Table.
setForwardingRulesForEastWestRouting(osRouter, osSubnetAdded, true);
}
private void handleRouterInterfacesRemoved(OpenstackRouter osRouter, OpenstackSubnet osSubnetRemoved) {
// Removes flow rules for routing among subnets connected to a router.
setRoutesAmongSubnets(osRouter, osSubnetRemoved, false);
// Removes flow rules for forwarding "packets going to external networks" to gateway nodes.
if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
setExternalConnection(osRouter, osSubnetRemoved, false);
}
// Removes flow rules to handle ping to the virtual gateway.
Ip4Address vGatewayIp = Ip4Address.valueOf(osSubnetRemoved.gatewayIp());
gatewayService.getGatewayDeviceIds()
.forEach(deviceId -> setGatewayIcmpRule(vGatewayIp, deviceId, false));
// Removes east-west routing rules for VMs in different Cnode to Switching Table.
setForwardingRulesForEastWestRouting(osRouter, osSubnetRemoved, false);
// Resets east-west routing rules for VMs in different Cnode to Switching Table.
routableSubNets(osRouter.id()).stream()
.forEach(subnet -> setForwardingRulesForEastWestRouting(osRouter, subnet, true));
}
private void setRoutesAmongSubnets(OpenstackRouter osRouter, OpenstackSubnet osSubnetAdded, boolean install) {
Set<OpenstackSubnet> routableSubNets = routableSubNets(osRouter.id());
if (routableSubNets.size() < 2) {
// no other subnet interface is connected to this router, do nothing
return;
}
Map<String, String> vniMap = new HashMap<>();
openstackService.networks().forEach(n -> vniMap.put(n.id(), n.segmentId()));
routableSubNets.stream()
.filter(subnet -> !subnet.id().equals(osSubnetAdded.id()))
.filter(subnet -> vniMap.get(subnet.networkId()) != null)
.forEach(subnet -> nodeService.completeNodes().stream()
.filter(node -> node.type().equals(COMPUTE))
.forEach(node -> {
setRoutingRules(node.intBridge(),
Integer.parseInt(vniMap.get(subnet.networkId())),
Integer.parseInt(vniMap.get(osSubnetAdded.networkId())),
subnet, osSubnetAdded, install);
setRoutingRules(node.intBridge(),
Integer.parseInt(vniMap.get(osSubnetAdded.networkId())),
Integer.parseInt(vniMap.get(subnet.networkId())),
osSubnetAdded, subnet, install);
}
));
}
private void setRoutingRules(DeviceId deviceId, int srcVni, int dstVni,
OpenstackSubnet subnetSrc, OpenstackSubnet subnetDst, boolean install) {
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchTunnelId(srcVni)
.matchIPSrc(IpPrefix.valueOf(subnetSrc.cidr()))
.matchIPDst(IpPrefix.valueOf(subnetDst.cidr()));
tBuilder.setTunnelId(dstVni);
RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, EW_ROUTING_RULE_PRIORITY, install);
// Flow rules for destination is in different subnet and different node,
// because VNI is converted to destination VNI in the source VM node.
sBuilder = DefaultTrafficSelector.builder();
tBuilder = DefaultTrafficTreatment.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchTunnelId(dstVni)
.matchIPSrc(IpPrefix.valueOf(subnetSrc.cidr()))
.matchIPDst(IpPrefix.valueOf(subnetDst.cidr()));
tBuilder.setTunnelId(dstVni);
RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, EW_ROUTING_RULE_PRIORITY, install);
}
private void setExternalConnection(OpenstackRouter osRouter, OpenstackSubnet osSubNet, boolean install) {
if (!osRouter.gatewayExternalInfo().isEnablePnat()) {
log.debug("Source NAT is disabled");
return;
}
//OpenstackSubnet osSubNet = openstackService.subnet(osSubNetId);
OpenstackNetwork osNet = openstackService.network(osSubNet.networkId());
nodeService.completeNodes().stream()
.filter(node -> node.type().equals(COMPUTE))
.forEach(node -> setRulesToGateway(
node.intBridge(),
gatewayService.getGatewayGroupId(node.intBridge()),
Long.valueOf(osNet.segmentId()), osSubNet.cidr(), install));
// Is this for PNAT ??
setRulesForGatewayToController(Long.valueOf(osNet.segmentId()), osSubNet.cidr(), install);
}
private void setRulesToGateway(DeviceId deviceId, GroupId groupId, long vni, String cidr, boolean install) {
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchTunnelId(vni)
.matchIPSrc(IpPrefix.valueOf(cidr))
.matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
tBuilder.group(groupId);
RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, ROUTING_RULE_PRIORITY, install);
}
private void setRulesForGatewayToController(long vni, String subNetCidr, boolean install) {
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchTunnelId(vni)
.matchIPSrc(IpPrefix.valueOf(subNetCidr))
.matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
tBuilder.setOutput(PortNumber.CONTROLLER);
gatewayService.getGatewayDeviceIds().forEach(deviceId ->
RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
tBuilder.build(), ForwardingObjective.Flag.VERSATILE, ROUTING_RULE_PRIORITY, install));
}
private void setGatewayIcmpRule(Ip4Address gatewayIp, DeviceId deviceId, boolean install) {
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_ICMP)
.matchIPDst(gatewayIp.toIpPrefix());
tBuilder.setOutput(PortNumber.CONTROLLER);
RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
tBuilder.build(), ForwardingObjective.Flag.VERSATILE, GATEWAY_ICMP_PRIORITY, install);
}
private void setForwardingRulesForEastWestRouting(OpenstackRouter router, OpenstackSubnet subnetAdded,
boolean install) {
Set<OpenstackSubnet> subnets = routableSubNets(router.id());
Set<Host> hosts = Tools.stream(hostService.getHosts())
.filter(h -> getVni(h).equals(openstackService.network(subnetAdded.networkId()).segmentId()))
.collect(Collectors.toSet());
subnets.stream()
.filter(subnet -> !subnet.id().equals(subnetAdded.id()))
.forEach(subnet -> getHosts(subnet)
.forEach(h -> setForwardingRulesAmongHostsInDifferentCnodes(h, hosts, install)));
}
private void setForwardingRulesAmongHostsInDifferentCnodes(Host host, Set<Host> remoteHosts, boolean install) {
Ip4Address localVmIp = getIp(host);
DeviceId localDeviceId = host.location().deviceId();
Optional<IpAddress> localDataIp = nodeService.dataIp(localDeviceId);
if (!localDataIp.isPresent()) {
log.debug("Failed to get data IP for device {}",
host.location().deviceId());
return;
}
remoteHosts.stream()
.filter(remoteHost -> !host.location().deviceId().equals(remoteHost.location().deviceId()))
.forEach(remoteVm -> {
Optional<IpAddress> remoteDataIp = nodeService.dataIp(remoteVm.location().deviceId());
if (remoteDataIp.isPresent()) {
setVxLanFlowRule(getVni(remoteVm),
localDeviceId,
remoteDataIp.get().getIp4Address(),
getIp(remoteVm), install);
setVxLanFlowRule(getVni(host),
remoteVm.location().deviceId(),
localDataIp.get().getIp4Address(),
localVmIp, install);
}
});
}
private void setVxLanFlowRule(String vni, DeviceId deviceId, Ip4Address remoteIp,
Ip4Address vmIp, boolean install) {
Optional<PortNumber> tunnelPort = nodeService.tunnelPort(deviceId);
if (!tunnelPort.isPresent()) {
log.warn("Failed to get tunnel port from {}", deviceId);
return;
}
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchTunnelId(Long.parseLong(vni))
.matchIPDst(vmIp.toIpPrefix());
tBuilder.extension(buildExtension(deviceService, deviceId, remoteIp), deviceId)
.setOutput(tunnelPort.get());
RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, SWITCHING_RULE_PRIORITY, install);
}
private OpenstackRouter openstackRouter(String routerId) {
return openstackService.routers().stream().filter(r ->
r.id().equals(routerId)).iterator().next();
}
@Override
public void reinstallVmFlow(Host host) {
// TODO: implements later
}
@Override
public void purgeVmFlow(Host host) {
// TODO: implements later
}
private class InternalNodeListener implements OpenstackNodeListener {
@Override
public void event(OpenstackNodeEvent event) {
OpenstackNode node = event.node();
switch (event.type()) {
case COMPLETE:
case INCOMPLETE:
log.info("COMPLETE node {} detected", node.hostname());
eventExecutor.execute(() -> {
if (node.type() == GATEWAY) {
GatewayNode gnode = GatewayNode.builder()
.gatewayDeviceId(node.intBridge())
.dataIpAddress(node.dataIp().getIp4Address())
.uplinkIntf(node.externalPortName().get())
.build();
gatewayService.addGatewayNode(gnode);
}
});
openstackService.routers().stream()
.forEach(router -> routableSubNets(router.id()).stream()
.forEach(subnet -> handleRouterInterfaces(router, subnet)));
break;
case INIT:
case DEVICE_CREATED:
default:
break;
}
}
}
}