blob: 95446190c8eb95754b408550b9b51a6281edf75d [file] [log] [blame]
/*
* Copyright 2015-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.segmentrouting.config;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.core.ApplicationId;
import org.onosproject.incubator.net.config.basics.ConfigException;
import org.onosproject.incubator.net.config.basics.InterfaceConfig;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.host.InterfaceIpAddress;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Segment Routing configuration component that reads the
* segment routing related configuration from Network Configuration Manager
* component and organizes in more accessible formats.
*/
public class DeviceConfiguration implements DeviceProperties {
private static final Logger log = LoggerFactory.getLogger(DeviceConfiguration.class);
private final List<Integer> allSegmentIds = new ArrayList<>();
private final Map<DeviceId, SegmentRouterInfo> deviceConfigMap = new ConcurrentHashMap<>();
private final Map<VlanId, List<ConnectPoint>> xConnects = new ConcurrentHashMap<>();
private ApplicationId appId;
private NetworkConfigService cfgService;
private class SegmentRouterInfo {
int nodeSid;
DeviceId deviceId;
Ip4Address ip;
MacAddress mac;
boolean isEdge;
Map<PortNumber, Ip4Address> gatewayIps;
SetMultimap<PortNumber, Ip4Prefix> subnets;
Map<Integer, Set<Integer>> adjacencySids;
public SegmentRouterInfo() {
gatewayIps = new HashMap<>();
subnets = HashMultimap.create();
}
}
/**
* Constructs device configuration for all Segment Router devices,
* organizing the data into various maps for easier access.
*
* @param appId application id
* @param cfgService config service
*/
public DeviceConfiguration(ApplicationId appId,
NetworkConfigRegistry cfgService) {
this.appId = appId;
this.cfgService = cfgService;
// Read config from device subject, excluding gatewayIps and subnets.
Set<DeviceId> deviceSubjects =
cfgService.getSubjects(DeviceId.class, SegmentRoutingDeviceConfig.class);
deviceSubjects.forEach(subject -> {
SegmentRoutingDeviceConfig config =
cfgService.getConfig(subject, SegmentRoutingDeviceConfig.class);
SegmentRouterInfo info = new SegmentRouterInfo();
info.deviceId = subject;
info.nodeSid = config.nodeSid();
info.ip = config.routerIp();
info.mac = config.routerMac();
info.isEdge = config.isEdgeRouter();
info.adjacencySids = config.adjacencySids();
deviceConfigMap.put(info.deviceId, info);
allSegmentIds.add(info.nodeSid);
});
// Read gatewayIps and subnets from port subject.
Set<ConnectPoint> portSubjects =
cfgService.getSubjects(ConnectPoint.class, InterfaceConfig.class);
portSubjects.forEach(subject -> {
// Do not process excluded ports
if (suppressSubnet().contains(subject)) {
return;
}
InterfaceConfig config =
cfgService.getConfig(subject, InterfaceConfig.class);
Set<Interface> networkInterfaces;
try {
networkInterfaces = config.getInterfaces();
} catch (ConfigException e) {
log.error("Error loading port configuration");
return;
}
networkInterfaces.forEach(networkInterface -> {
VlanId vlanId = networkInterface.vlan();
ConnectPoint connectPoint = networkInterface.connectPoint();
DeviceId dpid = connectPoint.deviceId();
PortNumber port = connectPoint.port();
SegmentRouterInfo info = deviceConfigMap.get(dpid);
// skip if there is no corresponding device for this ConenctPoint
if (info != null) {
// Extract subnet information
List<InterfaceIpAddress> interfaceAddresses = networkInterface.ipAddressesList();
interfaceAddresses.forEach(interfaceAddress -> {
// Do not add /0 and /32 to gateway IP list
int prefixLength = interfaceAddress.subnetAddress().prefixLength();
if (prefixLength != 0 && prefixLength != IpPrefix.MAX_INET_MASK_LENGTH) {
info.gatewayIps.put(port, interfaceAddress.ipAddress().getIp4Address());
}
info.subnets.put(port, interfaceAddress.subnetAddress().getIp4Prefix());
});
// Extract VLAN cross-connect information
// Do not setup cross-connect if VLAN is NONE
if (vlanId.equals(VlanId.NONE)) {
return;
}
List<ConnectPoint> connectPoints = xConnects.get(vlanId);
if (connectPoints != null) {
if (connectPoints.size() != 1) {
log.warn("Cross-connect should only have two endpoints. Aborting.");
return;
}
if (!connectPoints.get(0).deviceId().equals(connectPoint.deviceId())) {
log.warn("Cross-connect endpoints must be on the same switch. Aborting.");
return;
}
connectPoints.add(connectPoint);
} else {
connectPoints = new LinkedList<>();
connectPoints.add(connectPoint);
xConnects.put(vlanId, connectPoints);
}
}
});
});
}
@Override
public boolean isConfigured(DeviceId deviceId) {
return deviceConfigMap.get(deviceId) != null;
}
@Override
public int getSegmentId(DeviceId deviceId) throws DeviceConfigNotFoundException {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
if (srinfo != null) {
log.trace("getSegmentId for device{} is {}", deviceId, srinfo.nodeSid);
return srinfo.nodeSid;
} else {
String message = "getSegmentId fails for device: " + deviceId + ".";
throw new DeviceConfigNotFoundException(message);
}
}
/**
* Returns the Node segment id of a segment router given its Router mac address.
*
* @param routerMac router mac address
* @return node segment id, or -1 if not found in config
*/
public int getSegmentId(MacAddress routerMac) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
deviceConfigMap.entrySet()) {
if (entry.getValue().mac.equals(routerMac)) {
return entry.getValue().nodeSid;
}
}
return -1;
}
/**
* Returns the Node segment id of a segment router given its Router ip address.
*
* @param routerAddress router ip address
* @return node segment id, or -1 if not found in config
*/
public int getSegmentId(Ip4Address routerAddress) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
deviceConfigMap.entrySet()) {
if (entry.getValue().ip.equals(routerAddress)) {
return entry.getValue().nodeSid;
}
}
return -1;
}
@Override
public MacAddress getDeviceMac(DeviceId deviceId) throws DeviceConfigNotFoundException {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
if (srinfo != null) {
log.trace("getDeviceMac for device{} is {}", deviceId, srinfo.mac);
return srinfo.mac;
} else {
String message = "getDeviceMac fails for device: " + deviceId + ".";
throw new DeviceConfigNotFoundException(message);
}
}
@Override
public Ip4Address getRouterIp(DeviceId deviceId) throws DeviceConfigNotFoundException {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
if (srinfo != null) {
log.trace("getDeviceIp for device{} is {}", deviceId, srinfo.ip);
return srinfo.ip;
} else {
String message = "getRouterIp fails for device: " + deviceId + ".";
throw new DeviceConfigNotFoundException(message);
}
}
@Override
public boolean isEdgeDevice(DeviceId deviceId) throws DeviceConfigNotFoundException {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
if (srinfo != null) {
log.trace("isEdgeDevice for device{} is {}", deviceId, srinfo.isEdge);
return srinfo.isEdge;
} else {
String message = "isEdgeDevice fails for device: " + deviceId + ".";
throw new DeviceConfigNotFoundException(message);
}
}
@Override
public List<Integer> getAllDeviceSegmentIds() {
return allSegmentIds;
}
@Override
public Map<Ip4Prefix, List<PortNumber>> getSubnetPortsMap(DeviceId deviceId)
throws DeviceConfigNotFoundException {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
if (srinfo == null) {
String message = "getSubnetPortsMap fails for device: " + deviceId + ".";
throw new DeviceConfigNotFoundException(message);
}
// Construct subnet-port mapping from port-subnet mapping
SetMultimap<PortNumber, Ip4Prefix> portSubnetMap = srinfo.subnets;
Map<Ip4Prefix, List<PortNumber>> subnetPortMap = new HashMap<>();
portSubnetMap.entries().forEach(entry -> {
PortNumber port = entry.getKey();
Ip4Prefix subnet = entry.getValue();
if (subnet.prefixLength() == IpPrefix.MAX_INET_MASK_LENGTH) {
return;
}
if (subnetPortMap.containsKey(subnet)) {
subnetPortMap.get(subnet).add(port);
} else {
ArrayList<PortNumber> ports = new ArrayList<>();
ports.add(port);
subnetPortMap.put(subnet, ports);
}
});
return subnetPortMap;
}
@Override
public Map<VlanId, List<ConnectPoint>> getXConnects() {
return xConnects;
}
/**
* Returns the device identifier or data plane identifier (dpid)
* of a segment router given its segment id.
*
* @param sid segment id
* @return deviceId device identifier
*/
public DeviceId getDeviceId(int sid) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
deviceConfigMap.entrySet()) {
if (entry.getValue().nodeSid == sid) {
return entry.getValue().deviceId;
}
}
return null;
}
/**
* Returns the device identifier or data plane identifier (dpid)
* of a segment router given its router ip address.
*
* @param ipAddress router ip address
* @return deviceId device identifier
*/
public DeviceId getDeviceId(Ip4Address ipAddress) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
deviceConfigMap.entrySet()) {
if (entry.getValue().ip.equals(ipAddress)) {
return entry.getValue().deviceId;
}
}
return null;
}
/**
* Returns the configured port ip addresses for a segment router.
* These addresses serve as gateway IP addresses for the subnets configured
* on those ports.
*
* @param deviceId device identifier
* @return immutable set of ip addresses configured on the ports or null if not found
*/
public Set<Ip4Address> getPortIPs(DeviceId deviceId) {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
if (srinfo != null) {
log.trace("getSubnetGatewayIps for device{} is {}", deviceId,
srinfo.gatewayIps.values());
return ImmutableSet.copyOf(srinfo.gatewayIps.values());
}
return null;
}
/**
* Returns the configured subnet prefixes for a segment router.
*
* @param deviceId device identifier
* @return list of ip prefixes or null if not found
*/
public Set<Ip4Prefix> getSubnets(DeviceId deviceId) {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
if (srinfo != null) {
log.trace("getSubnets for device{} is {}", deviceId,
srinfo.subnets.values());
ImmutableSet.Builder<Ip4Prefix> builder = ImmutableSet.builder();
builder.addAll(srinfo.subnets.values());
SegmentRoutingAppConfig appConfig =
cfgService.getConfig(appId, SegmentRoutingAppConfig.class);
if (appConfig != null) {
if (deviceId.equals(appConfig.vRouterId().orElse(null))) {
builder.add(Ip4Prefix.valueOf("0.0.0.0/0"));
}
}
return builder.build();
}
return null;
}
/**
* Returns the configured non-/32 and non-/0 subnet on the given port,
* or null if no subnet has been configured on the port.
*
* @param deviceId device identifier
* @param pnum port identifier
* @return configured subnet on port, or null
*/
public Ip4Prefix getPortSubnet(DeviceId deviceId, PortNumber pnum) {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
if (srinfo != null) {
Optional<Ip4Prefix> result = srinfo.subnets.get(pnum).stream()
.filter(subnet ->
subnet.getIp4Prefix().prefixLength() != IpPrefix.MAX_INET_MASK_LENGTH &&
subnet.getIp4Prefix().prefixLength() != 0)
.findFirst();
return (result.isPresent()) ? result.get() : null;
}
return null;
}
/**
* Returns the router ip address of segment router that has the
* specified ip address in its subnets.
*
* @param destIpAddress target ip address
* @return router ip address
*/
public Ip4Address getRouterIpAddressForASubnetHost(Ip4Address destIpAddress) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
deviceConfigMap.entrySet()) {
for (Ip4Prefix prefix : entry.getValue().subnets.values()) {
if (prefix.contains(destIpAddress)) {
return entry.getValue().ip;
}
}
}
log.debug("No router was found for {}", destIpAddress);
return null;
}
/**
* Returns the router mac address of segment router that has the
* specified ip address as one of its subnet gateway ip address.
*
* @param gatewayIpAddress router gateway ip address
* @return router mac address or null if not found
*/
public MacAddress getRouterMacForAGatewayIp(Ip4Address gatewayIpAddress) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
deviceConfigMap.entrySet()) {
if (entry.getValue().gatewayIps.
values().contains(gatewayIpAddress)) {
return entry.getValue().mac;
}
}
log.debug("Cannot find a router for {}", gatewayIpAddress);
return null;
}
/**
* Checks if the host is in the subnet defined in the router with the
* device ID given.
*
* @param deviceId device identification of the router
* @param hostIp host IP address to check
* @return true if the host is within the subnet of the router,
* false if no subnet is defined under the router or if the host is not
* within the subnet defined in the router
*/
public boolean inSameSubnet(DeviceId deviceId, Ip4Address hostIp) {
Set<Ip4Prefix> subnets = getSubnets(deviceId);
if (subnets == null) {
return false;
}
for (Ip4Prefix subnet: subnets) {
// Exclude /0 since it is a special case used for default route
if (subnet.prefixLength() != 0 && subnet.contains(hostIp)) {
return true;
}
}
return false;
}
/**
* Returns the ports corresponding to the adjacency Sid given.
*
* @param deviceId device identification of the router
* @param sid adjacency Sid
* @return set of port numbers
*/
public Set<Integer> getPortsForAdjacencySid(DeviceId deviceId, int sid) {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
return srinfo != null ?
ImmutableSet.copyOf(srinfo.adjacencySids.get(sid)) :
ImmutableSet.copyOf(new HashSet<>());
}
/**
* Check if the Sid given is whether adjacency Sid of the router device or not.
*
* @param deviceId device identification of the router
* @param sid Sid to check
* @return true if the Sid given is the adjacency Sid of the device,
* otherwise false
*/
public boolean isAdjacencySid(DeviceId deviceId, int sid) {
SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
return srinfo != null && srinfo.adjacencySids.containsKey(sid);
}
public Set<ConnectPoint> suppressSubnet() {
SegmentRoutingAppConfig appConfig =
cfgService.getConfig(appId, SegmentRoutingAppConfig.class);
return (appConfig != null) ? appConfig.suppressSubnet() : ImmutableSet.of();
}
public Set<ConnectPoint> suppressHost() {
SegmentRoutingAppConfig appConfig =
cfgService.getConfig(appId, SegmentRoutingAppConfig.class);
return (appConfig != null) ? appConfig.suppressHost() : ImmutableSet.of();
}
}